实时车辆行人多目标检测与跟踪系统(含UI界面,Python代码)

news2024/11/17 0:54:36

算法架构:

目标检测:yolov5
目标跟踪:OCSort

其中, Yolov5 带有详细的训练步骤,可以根据训练文档,训练自己的数据集,及其方便。
另外后续 目标检测会添加 yolov7 、yolox,目标跟踪会添加 ByteTrack、deepsort等经典算法,代码主要部分添加了详细注释,方便自己学习。

一、简介

本文将详细介绍如何使用深度学习中的YOLOv5和OCTrack算法实现车辆、行人等多目标的实时检测和跟踪,并利用PyQt5设计了简约的系统UI界面。在界面中,您可以选择自己的视频文件进行检测和跟踪,也可以通过电脑自带的摄像头进行实时处理。此外,您还可以更换自己训练的yolov5模型,进行自己数据的跟踪。

该系统界面优美,检测精度高,功能强大。它具备多目标实时检测、跟踪和计数功能,同时可以自由选择感兴趣的跟踪目标。

本博文提供了完整的Python程序代码和使用教程,适合新入门的朋友参考。您可以在文末的下载链接中获取完整的代码资源文件。以下是本博文的目录:

目录

  • 一、简介
  • 二、效果展示
  • 三、环境安装
  • 四、YOLOV5介绍
    • yolov5模型的训练步骤
  • 五、OCtrack介绍
  • 六、下载链接

二、效果展示

三、环境安装

以链接下均是博主个人的博客,总结的非常详细,按照此步骤安装,一定可以安装成功,相信自己欧。(后续会出详细的安装视频)

见 代码资源中 README.md 文件中 环境安装部分,以下是文档截图:
在这里插入图片描述

四、YOLOV5介绍

从实际效果来看YOLOv5已经相当优秀,是发展的比较完备、使用比较广泛的一个版本;而更重要的是YOLOv5的调用、训练和预测都十分方便。YOLOv5的另一个特点就是它为不同的设备需求和不同的应用场景提供了大小和参数数量不同的网络。

以下是YoloV5改进的部分(不完全):

(1)主干部分:使用了Focus网络结构,具体操作是在一张图片中每隔一个像素拿到一个值,这个时候获得了四个独立的特征层,然后将四个独立的特征层进行堆叠,此时宽高信息就集中到了通道信息,输入通道扩充了四倍。该结构在YoloV5第5版之前有所应用,最新版本中未使用。
(2)数据增强:Mosaic数据增强、Mosaic利用了四张图片进行拼接实现数据中增强,根据论文所说其拥有一个巨大的优点是丰富检测物体的背景!且在BN计算的时候一下子会计算四张图片的数据!
(3)多正样本匹配:在之前的Yolo系列里面,在训练时每一个真实框对应一个正样本,即在训练时,每一个真实框仅由一个先验框负责预测。YoloV5中为了加快模型的训练效率,增加了正样本的数量,在训练时,每一个真实框可以由多个先验框负责预测。

一、整体结构解析

在这里插入图片描述

和之前版本的Yolo类似,整个YoloV5可以依然可以分为三个部分,分别是Backbone,FPN以及Yolo Head

Backbone可以被称作YoloV5的主干特征提取网络,根据它的结构以及之前Yolo主干的叫法,我一般叫它CSPDarknet,输入的图片首先会在CSPDarknet里面进行特征提取,提取到的特征可以被称作特征层,是输入图片的特征集合。在主干部分,我们获取了三个特征层进行下一步网络的构建,这三个特征层我称它为有效特征层

FPN可以被称作YoloV5的加强特征提取网络,在主干部分获得的三个有效特征层会在这一部分进行特征融合,特征融合的目的是结合不同尺度的特征信息。在FPN部分,已经获得的有效特征层被用于继续提取特征。在YoloV5里依然使用到了Panet的结构,我们不仅会对特征进行上采样实现特征融合,还会对特征再次进行下采样实现特征融合。

Yolo Head是YoloV5的分类器与回归器,通过CSPDarknet和FPN,我们已经可以获得三个加强过的有效特征层。每一个特征层都有宽、高和通道数,此时我们可以将特征图看作一个又一个特征点的集合,每一个特征点都有通道数个特征。Yolo Head实际上所做的工作就是对特征点进行判断,判断特征点是否有物体与其对应。与以前版本的Yolo一样,YoloV5所用的解耦头是一起的,也就是分类和回归在一个1X1卷积里实现。

因此,整个YoloV5网络所作的工作就是 特征提取-特征加强-预测特征点对应的物体情况

yolov5模型的训练步骤

我们以 VOC 数据格式进行训练,如果自己的数据集是其他格式,可以通过https://blog.csdn.net/qq_28949847/article/details/130246098这篇博客进行格式转换。

1. 准备数据集(以VOC.yaml数据集为例)
2. 使用datasets文件夹下的voc2v5.py 将xml文件转为txt文件
只需修改下方红框位置处的参数, 具体含义见注释。
在这里插入图片描述

3. 参照datasets文件夹下创建的示例,创建文件夹结构,并将数据集放入在对应的文件夹下,截图如下:

在这里插入图片描述
4. 修改 data文件夹下的VOC.yaml

 # 数据集路径
 path: D:/lg/BaiduSyncdisk/project/person_code/project_self/Yolov5_OCtrack/datasets/airplane
 train: # train数据集
   - train
 val: # val 数据集
   - train
 test: # test 数据集
   - train

 # 修改为自己的类
 names:
   0: airplane

在这里插入图片描述

5. 根据自己的需求,修改 train.py文件夹下的参数,主要是以下画红框的这几个

在这里插入图片描述

6. 修改完参数后,点击运行即可训练
在这里插入图片描述

7. 训练结果(模型、PR曲线、数据分布情况、训练loss变化过程等)保存在 runs 文件夹下

在这里插入图片描述
在这里插入图片描述

五、OCtrack介绍

我们这里不做详细的原理介绍了,大部分的博客都已对其原理进行了详细介绍。我们主要对代码进行了详细的注释,有助于帮助我们更好的理解代码。其核心主要是两个类 OCSortKalmanBoxTracker, 下面代码对其进行了详细的注释:


class OCSort(object):
    def __init__(self, det_thresh, max_age=50, min_hits=-1,
                 iou_threshold=0.3, delta_t=3, asso_func="diou", inertia=0.2, use_byte=False):
        """
        初始化OCSort对象,并设置关键参数。

        参数:
        - det_thresh: float,目标检测结果的阈值
        - max_age: int,允许的跟踪器未更新的最大帧数,默认为30
        - min_hits: int,跟踪器更新所需的最小帧数,默认为3
        - iou_threshold: float,用于关联跟踪器和检测结果的IOU(交并比)阈值,默认为0.3
        - delta_t: int,时间步长,默认为3
        - asso_func: str,关联函数的名称,默认为"diou"
        - inertia: float,跟踪器的惯性权重,默认为0.2
        - use_byte: bool,是否使用字节级别的特征,默认为False
        """
        self.max_age = max_age
        self.min_hits = min_hits
        self.iou_threshold = iou_threshold
        # 存储跟踪器的列表
        self.trackers = []
        self.frame_count = 0
        self.det_thresh = det_thresh
        # 时间步长
        self.delta_t = delta_t
        self.asso_func = ASSO_FUNCS[asso_func]
        # 跟踪器的惯性权重
        self.inertia = inertia
        self.use_byte = use_byte
        # 跟踪器对象的计数器
        KalmanBoxTracker.count = 0
        self.label = {}
        # 存储label 对应关系
        self.idx_to_label = {}

    def update(self, results, img_size):
        """
            更新跟踪器状态。

            参数:
            - results: numpy数组,表示检测结果,格式为[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
            - img_size: 元组,表示图像的尺寸 (height, width)

            要求:即使没有检测结果,每帧都必须调用此方法(对于没有检测结果的帧,请使用 np.empty((0, 5)))。
            返回一个类似的数组,其中最后一列是对象的ID。
            注意:返回的对象数量可能与提供的检测数量不同。
        """

        # img 大小
        img_h, img_w = img_size[0], img_size[1]
        # 当前帧数
        self.frame_count += 1
        # 将 目标检测结果[[label, conf, [x1, y1, x2, y2],...] 转为 [[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]格式
        output_results = []
        for r_i, res in enumerate(results):
            label, conf, bbox = res[:3]
            if label not in self.label:
                self.label[label] = 0 if len(self.label) == 0 else (max(self.label.values()) + 1)
                self.idx_to_label = {v: k for k, v in self.label.items()}
            cls = self.label[label]
            output_results.append([bbox[0], bbox[1], bbox[2], bbox[3], conf, cls])

        output_results = np.array(output_results) if len(output_results) else np.empty((0, 6))
        # x1y1x2y2
        bboxes = output_results[:, :4]
        scores = output_results[:, 4]
        classes = output_results[:, 5:6]
        dets = np.concatenate((bboxes, np.expand_dims(scores, axis=-1), classes), axis=1)
        # 选出self.det_thresh > score > 0.1阈值的数据,为低分数据, 设置 BYTE association 为 True 时会用到
        inds_low = scores > 0.1
        inds_high = scores < self.det_thresh
        inds_second = np.logical_and(inds_low, inds_high)
        dets_second = dets[inds_second]

        # 选出 score > self.det_thresh 的数据
        remain_inds = scores > self.det_thresh
        dets = dets[remain_inds]

        # 存储卡尔曼预测后的位置信息
        trks = np.zeros((len(self.trackers), 5))
        to_del = []
        ret = []

        # 对已经存在的self.trackers中的跟踪器进行卡尔曼位置预测
        for t, trk in enumerate(trks):
            # 根据trackers中的信息,进行卡尔曼位置预测
            pos = self.trackers[t].predict()[0]
            # 更新跟踪器的位置信息
            trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
            # 如果预测的位置出现NaN值(无效值)
            if np.any(np.isnan(pos)):
                # 将该跟踪器的索引添加到待删除列表中
                to_del.append(t)
        # 移除包含NaN等无效值的行
        trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
        # 从跟踪器列表中删除无效的跟踪器, 逆序删除元素, 防止索引发送变化
        for t in reversed(to_del):
            self.trackers.pop(t)

        # 获取跟踪器的速度
        velocities = np.array([trk.velocity if trk.velocity is not None else np.array((0, 0)) for trk in self.trackers])
        # 获取最后观察到的边界框
        last_boxes = np.array([trk.last_observation for trk in self.trackers])
        # 获取K个先前观察结果 ???
        k_observations = np.array([k_previous_obs(trk.observations, trk.age, self.delta_t) for trk in self.trackers])

        """
            第一次:
            将目标检测得到的box同卡尔曼预测后的box进行级联匹配
        """
        matched, unmatched_dets, unmatched_trks = associate(dets, trks, self.iou_threshold, velocities, k_observations,
                                                            self.inertia)
        for m in matched:
            # 根据最新匹配的box进行卡尔曼更新
            self.trackers[m[1]].update(dets[m[0], :])

        """
            # BYTE association
            Second round of associaton by OCR
            对未匹配的追踪器 同 低分的box进行2次匹配
        """
        if self.use_byte and len(dets_second) > 0 and unmatched_trks.shape[0] > 0:
            # 提取未匹配的追踪器
            u_trks = trks[unmatched_trks]
            # 计算低分检测结果和未匹配追踪器之间的IOU
            iou_left = self.asso_func(dets_second, u_trks)  # iou between low score detections and unmatched tracks
            iou_left = np.array(iou_left)

            # 检查最大的IOU是否超过阈值
            if iou_left.max() > self.iou_threshold:
                """
                    注意:通过使用较低的阈值,例如 self.iou_threshold - 0.1,在MOT17/MOT20数据集上可能会获得更高的性能。
                    但出于简单起见,我们在这里保持阈值的一致性。
                """
                # 使用线性分配算法进行匹配
                matched_indices = linear_assignment(-iou_left)
                # 存储待删除的追踪器索引
                to_remove_trk_indices = []
                # 遍历匹配的索引对
                for m in matched_indices:
                    det_ind, trk_ind = m[0], unmatched_trks[m[1]]
                    # 如果IOU低于阈值,则跳过继续下一次迭代
                    if iou_left[m[0], m[1]] < self.iou_threshold:
                        continue
                    # 更新对应的追踪器状态
                    self.trackers[trk_ind].update(dets_second[det_ind, :])
                    # 将待删除的追踪器索引添加到列表中
                    to_remove_trk_indices.append(trk_ind)
                # 从未匹配的追踪器中移除已匹配的部分
                unmatched_trks = np.setdiff1d(unmatched_trks, np.array(to_remove_trk_indices))

        # 对未匹配的检测结果和追踪器 进行第2次重新匹配
        if unmatched_dets.shape[0] > 0 and unmatched_trks.shape[0] > 0:
            # 提取未匹配的检测结果和追踪器
            left_dets = dets[unmatched_dets]
            left_trks = last_boxes[unmatched_trks]
            # 计算未匹配的检测结果和追踪器之间的IOU
            iou_left = self.asso_func(left_dets, left_trks)
            iou_left = np.array(iou_left)
            # 检查最大的IOU是否超过阈值
            if iou_left.max() > self.iou_threshold:
                """
                    注意:通过使用较低的阈值,例如 self.iou_threshold - 0.1,在MOT17/MOT20数据集上可能会获得更高的准确度。
                    但出于简单起见,我们在这里保持阈值的一致性。
                """
                # 使用线性分配算法进行重新匹配
                rematched_indices = linear_assignment(-iou_left)
                # 存储待删除的检测结果和追踪器的索引
                to_remove_det_indices = []
                to_remove_trk_indices = []
                for m in rematched_indices:
                    det_ind, trk_ind = unmatched_dets[m[0]], unmatched_trks[m[1]]
                    # 如果IOU低于阈值,则跳过继续下一次迭代
                    if iou_left[m[0], m[1]] < self.iou_threshold:
                        continue
                    # 更新对应的追踪器状态,索引是一直对应的,所以此处可以直接更新
                    self.trackers[trk_ind].update(dets[det_ind, :])
                    # 将待删除的检测结果和追踪器索引添加到列表中
                    to_remove_det_indices.append(det_ind)
                    to_remove_trk_indices.append(trk_ind)
                # 从未匹配的检测结果和追踪器中移除已匹配的部分
                unmatched_dets = np.setdiff1d(unmatched_dets, np.array(to_remove_det_indices))
                unmatched_trks = np.setdiff1d(unmatched_trks, np.array(to_remove_trk_indices))

        # 没有匹配上的tracker,也就是在没有新的测量值时,传入None,冻结滤波器状态并保持先前的预测结果,但是里面的后验状态 self.x_post、后验协方差 self.P_post、残差 self.y 进行了更新
        for m in unmatched_trks:
            self.trackers[m].update(None)

        # 对未匹配上的目标检测框,创建一个新的 KalmanBoxTracker 跟踪器对象
        for i in unmatched_dets:
            trk = KalmanBoxTracker(dets[i, :], delta_t=self.delta_t)
            self.trackers.append(trk)
        i = len(self.trackers)

        for trk in reversed(self.trackers):
            # 如果跟踪器的最近观测的总和小于0(即没有有效的观测)
            if trk.last_observation.sum() < 0:
                d = trk.get_state()[0]
            else:
                '''
                使用最近的观测box还是卡尔曼滤波器的预测box是可选的,这里没有注意到显著的差异。
                '''
                # 跟踪器有最近的观测(last_observation),则将变量 d 设置为该观测的前四个元素(即边界框的box)
                d = trk.last_observation[:4]
            if (trk.time_since_update <= 3) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
                # +1 as MOT benchmark requires positive
                ret.append(np.concatenate((d, [trk.id, trk.cls, trk.conf])).reshape(1, -1))
            i -= 1
            # 当超过 max_age 没有更新时,删除此跟踪信息
            if trk.time_since_update > self.max_age:
                self.trackers.pop(i)

        outputs = []
        if len(ret) > 0:
            ret = np.concatenate(ret)
            for idx, t in enumerate(ret):
                x1, y1, x2, y2 = int(t[0]), int(t[1]), int(t[2]), int(t[3])
                track_id = int(t[4])
                cls = int(t[5])
                det_conf = t[6]
                class_name = self.idx_to_label[cls]

                x1 = min(img_w - 1, max(0, x1))
                x2 = min(img_w - 1, max(0, x2))
                y1 = min(img_h - 1, max(0, y1))
                y2 = min(img_h - 1, max(0, y2))
                res = [x1, y1, x2, y2, class_name, det_conf, track_id]
                outputs.append(res)

        return outputs
class KalmanBoxTracker(object):
    """
        这个类表示以bbox形式观察到的单个被跟踪对象的内部状态。
    """
    count = 0

    def __init__(self, bbox, delta_t=3, orig=False):
        """
            使用目标检测框box初始化跟踪器。

            参数:
            - bbox:表示要跟踪的对象的初始边界框(目标检测框)。
            - delta_t:时间间隔,用于估计速度,计算速度是用 当前的box - 倒数第delta_t的box。
            - orig:一个布尔值,如果orig为False,则使用自定义的KalmanFilterNew作为卡尔曼滤波器模型,
                    否则使用filterpy库中的KalmanFilter。

        """
        # 定义Kalman滤波器模型
        if not orig:
            from .kalmanfilter import KalmanFilterNew as KalmanFilter
            self.kf = KalmanFilter(dim_x=7, dim_z=4)
        else:
            from filterpy.kalman import KalmanFilter
            self.kf = KalmanFilter(dim_x=7, dim_z=4)
        # 设置状态转移矩阵 7 * 7 矩阵,通过状态转移矩阵和当前状态的乘积,可以预测对象在下一个时间步的状态。
        # 7个位置分别代表:cx(中心位置)、cy(中心位置)、宽度、高度、速度(水平)、速度(垂直)、加速度
        # 当状态转移矩阵中的元素被设置为1时,表示在系统的状态转移过程中,对应状态之间的关系是线性的,并且该状态在时间推进时保持不变。
        self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0],
                              [0, 1, 0, 0, 0, 1, 0],
                              [0, 0, 1, 0, 0, 0, 1],
                              [0, 0, 0, 1, 0, 0, 0],
                              [0, 0, 0, 0, 1, 0, 0],
                              [0, 0, 0, 0, 0, 1, 0],
                              [0, 0, 0, 0, 0, 0, 1]])
        # 设置观测矩阵
        self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0],
                              [0, 1, 0, 0, 0, 0, 0],
                              [0, 0, 1, 0, 0, 0, 0],
                              [0, 0, 0, 1, 0, 0, 0]])

        self.kf.R[2:, 2:] *= 10.
        # 对不可观测的初始速度给予较高的不确定性
        self.kf.P[4:, 4:] *= 1000.
        self.kf.P *= 10.
        self.kf.Q[-1, -1] *= 0.01
        self.kf.Q[4:, 4:] *= 0.01

        self.kf.x[:4] = convert_bbox_to_z(bbox)
        self.conf = bbox[4]
        self.time_since_update = 0
        self.id = KalmanBoxTracker.count
        KalmanBoxTracker.count += 1
        self.history = []
        self.hits = 0
        self.hit_streak = 0
        self.age = 0
        """
          注意:[-1,-1,-1,-1,-1]是一个妥协的占位符,表示非观测状态,
          对于函数k_previous_obs的返回值也是如此。虽然不够美观,但为了以快速和统一的方式支持生成观测数组,
          如下所示:k_observations = np.array([k_previous_obs(...)]])。
        """
        self.last_observation = np.array([-1, -1, -1, -1, -1])  # placeholder
        self.observations = dict()
        self.history_observations = []
        self.velocity = None
        self.delta_t = delta_t

        self.cls = int(bbox[5])


    def update(self, bbox):
        """
        Args:
        bbox (array or None): 观测到的边界框,可以是数组或 None。

        Returns:
            None

        Notes:
            - self.velocity的计算:
            - 对于每个时间步 dt,从当前时间步 self.age 开始向前查找,找到与当前观测 bbox 相隔 delta_t 步的观测数据。
            - 如果找不到与当前观测相隔 delta_t 步的观测数据,使用最近的观测数据作为之前的边界框。
        """
        if bbox is not None:
            if self.last_observation.sum() >= 0:  # no previous observation
                previous_box = None
                for i in range(self.delta_t):
                    dt = self.delta_t - i
                    if self.age - dt in self.observations:
                        previous_box = self.observations[self.age - dt]
                        break
                if previous_box is None:
                    previous_box = self.last_observation

                # 估计跟踪速度方向,使用与当前观测的bbox相隔 delta_t  步的观测数据
                self.velocity = speed_direction(previous_box, bbox)

            # 插入新的观测数据
            self.last_observation = bbox[:5]
            self.conf = bbox[4]

            self.observations[self.age] = bbox[:5]
            # 重置自上次更新以来的时间步数和历史记录
            self.time_since_update = 0
            self.history = []
            # 增加命中计数和命中连续次数
            self.hits += 1
            self.hit_streak += 1
            # 根据最新匹配的 bbox,进行卡尔曼更新
            self.kf.update(convert_bbox_to_z(bbox))

        else:
            self.kf.update(bbox)

    def predict(self):
        """
        对跟踪器的速度和宽度之和进行判断,如果其小于等于零,则将跟踪器的水平加速度置零。

        具体解释如下:
            self.kf.x[6] 表示状态向量中的第 7 个元素,即水平加速度。
            self.kf.x[2] 表示状态向量中的第 3 个元素,即宽度。
            self.kf.x[6] + self.kf.x[2] 表示跟踪器的水平加速度和宽度之和。
            if self.kf.x[6] + self.kf.x[2] <= 0: 判断跟踪器的水平加速度和宽度之和是否小于等于零。

            如果条件成立,即跟踪器的速度和宽度之和小于等于零,那么 self.kf.x[6] *= 0.0 将跟踪器的水平加速度置零,相当于将其速度减小为零或停止水平运动。
        """
        # 判断速度和宽度之和是否小于等于零
        if (self.kf.x[6] + self.kf.x[2]) <= 0:
            # 若小于等于零,将水平加速度置零
            self.kf.x[6] *= 0.0

        # 利用卡尔曼滤波器进行状态预测
        self.kf.predict()
        # 增加跟踪器的age
        self.age += 1
        # 如果自上次更新以来经过的时间步数大于零
        if self.time_since_update > 0:
            # 将命中计数置零
            self.hit_streak = 0
        # 增加自上次更新以来的时间步数
        self.time_since_update += 1
        # 将当前状态向量 self.kf.x 转换为边界框信息,并添加到历史记录中
        self.history.append(convert_x_to_bbox(self.kf.x))
        # 返回历史记录中的最后一个边界框作为预测的边界框估计
        return self.history[-1]

    def get_state(self):
        """
        Returns the current bounding box estimate.
        """
        return convert_x_to_bbox(self.kf.x)

目标跟踪效果:
在这里插入图片描述
至此视频中多目标检测跟踪的代码实现部分介绍完毕,后面的博文中将给出训练程序以及UI界面的详细介绍,至于程序如何使用、依赖包安装、pycharm软件的安装过程将通过博主的B站视频进行演示介绍,敬请关注!

六、下载链接

若您想获得博文中涉及的实现完整全部程序文件(包括模型权重,py, UI文件等,如下图),这里已打包上传至博主的面包多平台,已将所有涉及的文件同时打包到里面,点击即可运行,完整文件截图如下:
在这里插入图片描述
面包多:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1025831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单例模式(饿汉模式 懒汉模式)与一些特殊类设计

文章目录 一、不能被拷贝的类 二、只能在堆上创建类对象 三、只能在栈上创建类对象 四、不能被继承的类 五、单例模式 5、1 什么是单例模式 5、2 什么是设计模式 5、3 单例模式的实现 5、3、1 饿汉模式 5、3、1 懒汉模式 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x…

2023-09-20 Android CheckBox 让文字显示在选择框的左边

一、CheckBox 让文字在选择框的左边 &#xff0c;在布局文件里面添加下面一行就可以。 android:layoutDirection"rtl" 即可实现 android:paddingStart"10dp" 设置框文间的间距 二、使用的是left to right <attr name"layoutDirection">&…

无代码开发和低代码开发的本质区别

目录 一、两者的概念区别 二、两者面向的人群不同 三、集成能力的区别 四、扩展能力的区别 五、选购建议 无代码和低代码开发都是目前新兴的一种软件开发方式。 一、两者的概念区别 低代码开发&#xff08;Low-Code Development&#xff09;是一种通过使用图形界面和预先构建的…

机器学习入门教学——损失函数(最小二乘法)

1、前言 我们在训练神经网络时&#xff0c;最常用到的方法就是梯度下降法。在了解梯度下降法前&#xff0c;我们需要了解什么是损失(代价)函数。所谓求的梯度&#xff0c;就是损失函数的梯度。如果不知道什么是梯度下降的&#xff0c;可以看一下这篇文章&#xff1a;机器学习入…

【c语言】详解结构体

目录 什么是结构体&#xff1f;结构体的声明结构体变量的创建和初始化匿名结构体类型结构体的自引用结构体的初始化普通初始化指定初始化 结构体内存对齐对齐规则默认对齐数的修改 结构体传参 什么是结构体&#xff1f; 在学习每个类型之前我们需要了解其存在的意义&#xff0…

简单几个配置 Go 实现敏感数据脱敏,可以自定义数据脱敏规则(附完整实现源码)

简单几个配置 Go 实现敏感数据脱敏,可以自定义数据脱敏规则(附完整实现源码)。 介绍 为了保障企业的数据安全和隐私安全,godlp 提供了一系列针对敏感数据的识别和处置方案,其中包含敏感数据识别算法,数据脱敏处理方式,业务自定义的配置选项和海量数据处理能力。godlp 能…

全流程WRF高精度气象模拟技术及在地学领域应用教程

详情点击公众号链接&#xff1a;全流程WRF高精度气象模拟技术及在地学领域应用教程 前沿 气候是多个领域&#xff08;生态、水资源、风资源及碳中和等问题&#xff09;的主要驱动因素&#xff0c;合理认知气候变化有利于解释生态环境变化机理及过程&#xff0c;而了解现在、未…

【Spatial-Temporal Action Localization(六)】论文阅读2021年

文章目录 1. MultiSports: A Multi-Person Video Dataset of Spatio-Temporally Localized Sports Actions摘要和结论引言&#xff1a;针对痛点和贡献数据特点 2. Actor-Context-Actor Relation Network for Spatio-Temporal Action Localization摘要和结论引言&#xff1a;针对…

跨平台编译C代码问题之ARM平台char类型导致异常及其解决措施

目录 环境设备&#xff1a; 背景现象&#xff1a; 1、x86下的结果输出和打印输出 2、arm下的结果输出和打印输出 原因分析&#xff1a; 解决措施&#xff1a; 环境设备&#xff1a; x86的ubuntu、arm的麒麟 背景现象&#xff1a; 由于项目需要&#xff0c;需要将代码移植…

Linux编辑器-gcc的使用

一&#xff1a;背景知识 1.预处理&#xff08;头文件展开、去注释、宏替换、条件编译&#xff09; 2.编译&#xff08;由C生成汇编&#xff09; 3.汇编&#xff08;生成及其可识别代码&#xff09; 4.连接&#xff08;生成可执行文件或库文件&#xff09; 二&#xff1a;gcc…

解决ModuleNotFoundError: No module named ‘diffusers.models.cross_attention‘

目录 项目场景: 问题描述 原因分析: 解决方案: 方案一:

如何取消显示Notepad++每行显示的CRLF符号

新电脑中重新安装了Nodepad&#xff0c;打开记事本后发现出现了许多黑底的CR|LF标记&#xff0c;特别碍眼。 如何取消呢&#xff1f; 视图 -> 显示符号 -> 取消勾选 显示行尾符操作步骤 预期效果

秦丝9周年 | 各行业实体生意如何实现数字化转型?

近期&#xff0c;北京、深圳、天津、重庆等全国27个省都在推进“一刻钟便民生活圈”——以社区居民为服务对象&#xff0c;在步行15分钟左右的范围内&#xff0c;满足居民日常生活基本消费和品质消费。 而各行业的实体店是这个“圈”中的重要组成部分&#xff0c;很多入驻的实…

jupyter notebook进不去指定目录怎么办?

首先激活你要使用的虚拟环境 刚开始是现在 (base) C:\Users\lenovo>目录下 直接输入你想进入的盘 (base) C:\Users\lenovo>e:此时再cd (base) C:\Users\lenovo>cd E:\tim\learn_pytorch 就可以进入了 安装3.4.1.15问题 已经有了最新python版本的虚拟环境&#…

6-1 汉诺塔

汉诺&#xff08;Hanoi&#xff09;塔问题是一个经典的递归问题。 设有A、B、C三个塔座&#xff1b;开始时&#xff0c;在塔座A上有若干个圆盘&#xff0c;这些圆盘自下而上&#xff0c;由大到小地叠在一起。要求将塔座A上的圆盘移到塔座B上&#xff0c;并仍按同样顺序叠放。在…

微服务是个坏主意吗?

曾几何时&#xff0c;我记得我的手指疯狂地敲打键盘&#xff0c;与庞大而杂乱的代码库搏斗。那是巨石的时代&#xff0c;代码就像古老的城堡一样&#xff0c;由一块块石头砌成一个令人印象深刻的庞然大物。 几年过去了&#xff0c;时代变了。开发人员口中的流行语变成了“微服…

TS同时打包和监视所有ts文件或只指定ts文件

当我们项目中ts文件较多时&#xff0c;我们如何直接打包所有ts文件为js文件&#xff1f;而不是使用tsc 文件名 一个一个去打包文件 一、配置tsconfig.json文件 创建一个tsconfig.json文件&#xff0c;该文件中不需要配置任何信息 二、控制台输入打包命令 在控制台输入如下代…

手机机型响应式设置2

window.screen.height&#xff1a;屏幕高度 window.innerHeight&#xff1a;视口高度&#xff08;去除浏览器头尾的高度&#xff09; document.body.clientHeight&#xff1a;内容高度 vh&#xff1a;网页视口高度的1/100 vw&#xff1a;网页视口宽度的1/100 vmax&#xff…

2万多条汉字笔画笔顺查询ACCESS\EXCEL数据库

发现很多新华字典类的数据都没有笔顺的相关数据&#xff0c;因此就找了一下笔顺查询相关&#xff0c;发现有两个模式&#xff0c;一种是每个字每个笔画都有一张图片&#xff08;很庞大的图片数据量&#xff09;&#xff1b;一种是笔画图片文件显示型&#xff0c;比如今天采集的…

Vue的详细教程--基础语法【下】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Vue的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.事件处理器 ①.stop ②.prevent ③.on…