基于YOLOv5+Deepsort的鸡蛋检测系统 | 多区域可选定多目标跟踪应用

最近在研究openmmlab框架,学习了mmyolo的部署,恰好最近接手了一个鸡蛋检测的小项目,便打算用PyQt5+yolov5来完成这个项目,项目最终源码已经放在我的Github网址上,需要自取,麻烦点个star谢谢! 

Release egg_detection1.0 · ryhxf/egg_detection-YOLOv5-Deepsort · GitHub

演示视频链接:基于MMyolo框架的YOLOv5+Deepsort的鸡蛋检测系统 | 多区域可选定多目标跟踪应用 代码开源_哔哩哔哩_bilibili 

环境要求

本人参照mmyolo官方文档搭建的环境

15 分钟上手 MMYOLO 目标检测 — MMYOLO 0.6.0 文档

教程里搭建好环境,保证有mmyolo的本地代码库就行,后面会用到。

本人使用环境:

Pytorch为1.13.1,使用Cuda为11.7,cudnn为8.9.7

PyQt5==5.15.11

mmengine==0.10.5

mmdet==3.3.0

mmcv-full==1.7.2 

mmcv==2.0.1

deep-sort-realtime==1.3.2

其他缺失的库请自行补充,用pip install还是用mim install,这里就不反复赘述了。

代码检测基于mmyolo框架,把egg_detect.py和.pth权重文件放到mmyolo代码库中就行。

这里我是直接放在mmyolo目录下的

修改部分

拿到egg_detect.py代码后,需要修改config_path(mmyolo框架的专用运行配置文件yolov5_s-v61_syncbn_fast_1xb32-100e_egg.py)和checkpoint(训练完的模型权重文件best_coco_bbox_mAP_epoch_98.pth)更改为自己的本地路径。在代码400行左右。这两个文件我都放在Github工程里了。

    def init_model(self):
        try:
            config_path = 'F:\\openmmlab\\mmyolo\\configs\\yyx_egg\\yolov5_s-v61_syncbn_fast_1xb32-100e_egg.py'
            checkpoint_path = 'F:/openmmlab/mmyolo/best_coco_bbox_mAP_epoch_98.pth'

            config = Config.fromfile(config_path)
            if 'init_cfg' in config.model.backbone:
                config.model.backbone.init_cfg = None

            if torch.cuda.is_available():
                device = 'cuda:0'
                print("使用GPU进行运算")
            else:
                device = 'cpu'
                print("CUDA 不可用,使用 CPU 进行运算")

            self.model = init_detector(config, checkpoint_path, device=device, cfg_options={})
            self.visualizer = VISUALIZERS.build(self.model.cfg.visualizer)
            self.visualizer.dataset_meta = self.model.dataset_meta
        except Exception as e:
            print(f"初始化模型时发生错误: {e}")

然后,打开yolov5_s-v61_syncbn_fast_1xb32-100e_egg.py文件,在代码第一行。

_base_ = '../yolov5/yolov5_s-v61_syncbn_fast_8xb16-300e_coco.py'

在mmyolo的代码框架下,相对路径能检测到,保险起见,还是修改成自己本地的绝对路径。下面是我自己的本地路径,大家在安装好环境的mmyolo的代码库找到对应文件,直接复制路径粘贴修改即可。

_base_ = 'F:\openmmlab\mmyolo\configs\yolov5\yolov5_s-v61_syncbn_fast_8xb16-300e_coco.py'

图中为需要复制的文件路径,自己在安装好的MMYOLO库中按图中目录找到就行。

可能有人会问,为啥配置文件yolov5_s-v61_syncbn_fast_1xb32-100e_egg.py的其他地方不改,就改第一行,其他代码对应的路径文件我也没有啊。但其实没有事,因为其他部分对应的是模型的训练部分,这里已经跳过训练部分,直接用我现成的模型权重文件就行了。

这两块地方改完,代码就应该没问题了。

激活自己的虚拟环境后,运行

python egg_detect.py

运行效果功能展示

运行成功后出来界面是这样的:

支持导入视频进行检测和鸡蛋计数跟踪。

点击“载入视频”按钮,导入视频后会显示视频的第一帧画面。

这个项目采用了1个检测器(YOLOv5)和3个跟踪器(Deepsort)

self.model = init_detector(config, checkpoint_path, device=device, cfg_options={})
self.tracker_enter = DeepSort(max_age=30)  # 初始化 Deep SORT 跟踪器
self.tracker_exit1 = DeepSort(max_age=50, n_init = 1, nn_budget = 30)  # 初始化 Deep SORT 跟踪器
self.tracker_exit2 = DeepSort(max_age=30)  # 初始化 Deep SORT 跟踪器

检测是针对全图frame变量进行检测

3个跟踪器分别对应三个需要划定的区域,一个入口和两个出口(为啥是俩出口,因为考虑到可能出现在实际应用中会有分流的情况,对此进行模拟)

使用前需要先划定好需要想要的区域,先点击“划定入口区域”,在图像中依次点击区域的两个点即可。

划定一个区域后,点击“完成划定”,保存区域坐标点。当然命令行窗口也会显示信息。

另外两个区域以此类推。

完成所有区域划定后,点击“开始检测”,就会自动开始检测。

同时会将划定区域的图像(frame1,frame2,frame3)也单独显示出来,同时显示其他信息。

点击“停止检测”,可以显示入口区域已经经过的鸡蛋数量和另外俩区域的数量,并算出当前传送带中的剩余鸡蛋(剩余=入口-出口1-出口2,根据自己需要可修改),同时计算流量(这里是根据入口区域的鸡蛋计算流量),记录的“已检测时间”也会自动停止计时。继续运行按“开始检测”。

如果发现一开始划定的区域位置有偏移,点击“停止检测”,再重新划定需要的区域,再点击“开始检测”继续运行。(当然实际应用的时候,摄像头肯定是固定的,不过也要考虑实际运行需要修改的情况)

运行一定时间可以查看结果。

代码同目录下会自动生成目录save_result,里面存放每次运行的数据,以egg_result_当前时间命名,保存为csv文件,可以在本地用Excel打开,每10秒记录一次。

如果想要直接导出到想要的文件夹,点击“导出结果”

可以自己选择保存的地址,终端也会打印保存的信息。

完成一段检测后,可以点击“清空数据”,这时候会清空之前运行的缓存数据(不会清除csv数据文件),此时可以重新开始运行。

当然,如果仅仅是单入单出的普通情况,只划定两个区域也能正常运行,运行如图。

代码讲解

下面简述一下代码,这里不做全部的展示,毕竟整个代码太屎了,并不简洁,这里只讲我写的时候重要部分。

定时器设置

        self.timer = QTimer()  # 创建定时器
        self.timer.timeout.connect(self.update_frame)  # 定时器中断函数 update_frame
        self.save_timer = QTimer()  # 创建定时器
        self.save_timer.timeout.connect(self.save_results)  # 定时器中断函数 save_results

def __init__(self)的定时器部分,这里的self.update_frame函数就是负责图像检测和跟踪的主要部分,self.save_results函数是保存结果功能。

def __init__(self)的跟踪器初始化部分,这里可以做自行修改,具体调节参数可以问GPT看含义,之所以exit1的参数不一样,是因为那个出口鸡蛋是直接快速滚下去的(实验条件简陋,就只用纸板立了个桥),滚的速度快,默认参数的DeepSort跟踪器追不上,会出现计数失误,所以需要根据实际情况去调节参数,不存在万能的情况。

self.tracker_enter = DeepSort(max_age=30)  # 初始化 Deep SORT 跟踪器
self.tracker_exit1 = DeepSort(max_age=50, n_init = 1, nn_budget = 30)  # 初始化 Deep SORT 跟踪器
self.tracker_exit2 = DeepSort(max_age=30)  # 初始化 Deep SORT 跟踪器

界面布局

 initUI(self)的页面布局部分(包括多画面显示,按钮和信息显示)

        # 设置布局
        layout = QVBoxLayout()
        layout.addWidget(self.load_button)
        layout.addWidget(self.start_button)
        layout.addWidget(self.stop_button)
        layout.addWidget(self.entry_button)
        layout.addWidget(self.exit1_button)
        layout.addWidget(self.exit2_button)
        layout.addWidget(self.end_polygon_button)
        layout.addWidget(self.export_button)  
        layout.addWidget(self.clear_button) 
        layout.addWidget(self.egg_count_label)
        layout.addWidget(self.egg_enter_label)
        layout.addWidget(self.egg_exit1_label)
        layout.addWidget(self.egg_exit2_label)
        layout.addWidget(self.flow_rate_label)
        layout.addWidget(self.time_label)
        # layout.addWidget(self.speed_label) # 计速功能有问题,暂时不显示,后续修复

        frame_three = QHBoxLayout()
        frame_three.addWidget(self.label_frame1)
        frame_three.addWidget(self.label_frame2)
        frame_three.addWidget(self.label_frame3)

        frame_all = QVBoxLayout()
        frame_all.addWidget(self.label)
        frame_all.addLayout(frame_three)

        system = QHBoxLayout()
        system.addLayout(frame_all)
        system.addLayout(layout)

        self.setLayout(system)

这里注释了计速部分,是因为本人一开始还想加一个计算鸡蛋运行的平均速度(做成X像素/秒->X米/秒的形式),但是算出来速度太不稳定了,也懒得再花时间调试了,因为本人认为该功能不是很必需(绝不是因为菜555)

导入视频和划定区域

    def load_video(self):
        try:
            options = QFileDialog.Options()
            file_name, _ = QFileDialog.getOpenFileName(self, "Open Video File", "", "Videos (*.mp4 *.avi *.mov)", options=options)
            if file_name:
                print(f"加载视频文件: {file_name}")
                self.video_reader = mmcv.VideoReader(file_name)  # 使用 mmcv.VideoReader 读取视频文件
                
                # 读取第一帧并显示
                first_frame = self.video_reader.read()
                if first_frame is not None:
                    self.display_frame(first_frame)  # 显示第一帧
                
                self.init_model()  # 初始化模型
                self.start_video_thread()  # 启动视频读取线程
        except Exception as e:
            print(f"加载视频时发生错误: {e}")

这里加入了载入后停留显示第一帧的功能,因为需要根据画面划定区域。

    def start_rectangle_drawing(self, region_type):
        """开始绘制矩形区域"""
        self.rectangle_points = []
        self.is_drawing_rectangle = True
        self.current_region_type = region_type  # 保存当前区域类型

    def end_rectangle_drawing(self):
        """结束绘制矩形区域"""
        if len(self.rectangle_points) == 2:
            if self.current_region_type == 'entry':
                self.entry_region = self.rectangle_points
                self.zone_1 = 0  # 划定区域后,将区域标志位设为0,表示划定
            elif self.current_region_type == 'exit1':
                self.exit1_region = self.rectangle_points
                self.zone_2 = 0  # 划定区域后,将区域标志位设为0,表示划定
            elif self.current_region_type == 'exit2':
                self.exit2_region = self.rectangle_points
                self.zone_3 = 0  # 划定区域后,将区域标志位设为0,表示划定
            print(f"{self.current_region_type} 区域矩形已设定")

            # 确保 x1 < x2 和 y1 < y2
            for region in [self.entry_region, self.exit1_region, self.exit2_region]:
                if region and len(region) == 2:
                    pt1, pt2 = region
                    x1, y1 = min(pt1[0], pt2[0]), min(pt1[1], pt2[1])
                    x2, y2 = max(pt1[0], pt2[0]), max(pt1[1], pt2[1])
                    region[:] = [(x1, y1), (x2, y2)]  # 更新区域坐标
                    
        else:
            print("请确保绘制完整的矩形区域。")

这里是划定区域俩按钮的功能函数,这里只设置了3个区域,如果需要增加需求,在这里按顺序添加,当然其他相关的self参数都需要在init()函数中提前初始化,改的话照猫画虎就行。

载入模型

    def init_model(self):
        try:
            config_path = 'F:\\money_project\\openmmlab\\mmyolo\\configs\\yyx_egg\\yolov5_s-v61_syncbn_fast_1xb32-100e_egg.py'
            checkpoint_path = 'F:/money_project/openmmlab/mmyolo/best_coco_bbox_mAP_epoch_98.pth'

            config = Config.fromfile(config_path)
            if 'init_cfg' in config.model.backbone:
                config.model.backbone.init_cfg = None

            if torch.cuda.is_available():
                device = 'cuda:0'
                print("使用GPU进行运算")
            else:
                device = 'cpu'
                print("CUDA 不可用,使用 CPU 进行运算")

            self.model = init_detector(config, checkpoint_path, device=device, cfg_options={})
            self.visualizer = VISUALIZERS.build(self.model.cfg.visualizer)
            self.visualizer.dataset_meta = self.model.dataset_meta
        except Exception as e:
            print(f"初始化模型时发生错误: {e}")

这里可以修改运行设备,配置文件和模型权重文件的路径,本来在mm框架下,这些应该是在命令行中输入的,但是我觉得太麻烦了,就修改了下,嵌入到代码里了。当然如果有想二次开发的同学可以把俩文件导入也做成界面按钮导入的形式,那样更方便自由操作。

保存结果

    def save_results(self):
        """保存当前结果到CSV文件"""
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        result = {
            'time': current_time,
            'enter_egg': self.enter_egg,
            'exit1_egg': self.exit1_egg,
            'exit2_egg': self.exit2_egg,
            'flow_rate': self.flow_rate
        }
        self.results.append(result)

        # 确保保存目录存在
        save_dir = os.path.join(os.path.dirname(__file__), 'save_result')
        os.makedirs(save_dir, exist_ok=True)

        # 使用当前时间作为文件名的一部分
        file_time = datetime.now().strftime("%Y%m%d_%H%M%S")
        save_path = os.path.join(save_dir, f'egg_result_{file_time}.csv')
        
        with open(save_path, 'w', newline='') as csvfile:
            fieldnames = ['time', 'enter_egg', 'exit1_egg', 'exit2_egg', 'flow_rate']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(self.results)
        print(f"结果已保存到 {save_path}")

这里是保存文件部分,这里只设置了这几个变量保存,如果想要保存其他变量信息,可以自行修改。

开始检测

    def start_detection(self):
        """开始检测"""
        if self.entry_region == []:
            self.zone_1 = 1 # 如果没有划定区域,将区域标志位设为1,表示不划定

        if self.exit1_region == []:
            self.zone_2 = 1 # 如果没有划定区域,将区域标志位设为1,表示不划定

        if self.exit2_region == []:
            self.zone_3 = 1 # 如果没有划定区域,将区域标志位设为1,表示不划定
        # 打印3个标志位
        print(f"zone_1: {self.zone_1}, zone_2: {self.zone_2}, zone_3: {self.zone_3}")
        
        if not self.video_reader:
            print("视频文件未加载")
            return

        # 如果定时器未启动,或者之前已停止,重新启动定时器
        if not self.timer.isActive():
            self.timer.start(1000 // self.video_reader.fps)
            print("开始检测")

        # 如果线程已经停止,需要重新启动视频读取线程
        if self.stop_flag:
            self.stop_flag = False
            self.start_video_thread()
        
        
        if self.x == 0: # 此条件只执行一次
            self.x = 1
            self.detect_stoptime = time.time() # 记录首次暂停时间
            self.start_time = time.time()  # 记录首次开始检测时间

        self.save_timer.start(10000)  # 每10秒保存一次结果定时器开启
        
        self.stop_longtime += time.time() - self.detect_stoptime # 记录这一段暂停的时间并累加

“开始检测”按钮的功能函数,这个函数的功能比较多,这里会对没有划定的区域进行标志位(self.zone_1)修改,这样后面根据标志位判断是否需要处理该区域,防止因有区域未划定,代码运行报错。

同时这里也设定了两个定时器的参数,例如save_timer定时器可以设置多久保存一次数据。

self.stop_longtime += time.time() - self.detect_stoptime # 记录这一段暂停的时间并累加

这一行是用于后面计算检测时间用的,因为要考虑停止检测的时候,停止的时间需要去掉。

update_frame(self)图像处理部分

        if self.model and not self.frame_queue.empty():
            threshold = 0.9
            frame = self.frame_queue.get()  # 从队列中获取一帧
            h, w = frame.shape[:2]  # 获取帧的高度和宽度

            try:

这里是抽帧处理环节,获取帧的高和宽,其中threshold参数是检测时,需要滤除检测正确率低的标签结果的阈值,因为模型检测的任务与环境比较单一,所以训练效果极好,阈值就可以设置的很高。

            try:
                # print(f"Frame size: {w}x{h}")

                # 初始化区域帧变量
                frame1, frame2, frame3 = None, None, None
                entry_polygon, exit1_polygon, exit2_polygon = None, None, None  # 初始化为 None
                # 提取入口区域
                if len(self.entry_region) == 2 and self.zone_1 == 0:
                    x1, y1 = self.entry_region[0]
                    x2, y2 = self.entry_region[1]

                    x1, x2 = sorted([x1, x2])
                    y1, y2 = sorted([y1, y2])

                    if x1 < 0 or x2 > w or y1 < 0 or y2 > h:
                        raise ValueError("Entry region out of bounds.")
                    entry_polygon = np.array([
                        [x1, y1],  # 左上角
                        [x2, y1],  # 右上角
                        [x2, y2],  # 右下角
                        [x1, y2]   # 左下角
                    ])
                    frame1 = frame[y1:y2, x1:x2]  # 提取区域
                    # print(f"Frame1 size: {frame1.shape}")

这里是针对一开始划定区域的两个坐标点,提取整个区域(后面界面显示图像用到)和4个点的坐标点(后面cv2.pointPolygonTest函数会用到)

                # 对所有区域进行检测
                result = inference_detector(self.model, frame)

                # 获取全图检测结果
                if result is not None and result.pred_instances.bboxes.numel() > 0:
                    self.bboxes = result.pred_instances.bboxes.cpu().numpy()
                    self.scores = result.pred_instances.scores.cpu().numpy()
                else:
                    print("No detections in frame or result is invalid")

                # 全图检测结果
                detections = [
                    [
                        [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]],  # (left, top, width, height)
                        float(score),  # 置信度
                        0  # 类别(假设是单一类别)
                    ]
                    for bbox, score in zip(self.bboxes, self.scores) if score > threshold
                ]

这里是目标检测的部分,并获取检测的每一个标签存储在detection变量中。

                filtered_enter = []
                filtered_exit1 = []
                filtered_exit2 = []

                for detection in detections:
                    bbox, score, _ = detection
                    if score > threshold:
                        center_x = bbox[0] + (bbox[2] / 2)
                        center_y = bbox[1] + (bbox[3] / 2)
                        # print(f"yyz: {bbox[2] - bbox[0], bbox[3] - bbox[1]}")
                        center_point = (center_x, center_y)
                        # print(f"中心点: {center_point}")
                        # print(f"入口区域: {entry_polygon}")
                        # print(f"出口1区域: {exit1_polygon}")
                        # print(f"出口2区域: {exit2_polygon}")
                        # 画出中心点和entry_polygon
                        cv2.circle(frame, (int(center_x), int(center_y)), 5, (0, 0, 255), -1)
                        # 画出bbox
                        # print(f"bbox: {bbox}")
                        # cv2.polylines(frame, [boxx], isClosed=True, color=(0, 255, 0), thickness=2)
                        # cv2.rectangle(frame, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3]), (255, 0, 0), 2))
                        if self.zone_1 == 0: 
                            cv2.polylines(frame, [entry_polygon], isClosed=True, color=(0, 255, 0), thickness=2)
                        if self.zone_2 == 0:
                            cv2.polylines(frame, [exit1_polygon], isClosed=True, color=(0, 255, 0), thickness=2)
                        if self.zone_3 == 0:
                            cv2.polylines(frame, [exit2_polygon], isClosed=True, color=(0, 255, 0), thickness=2)
                        current_time = time.time()
                        # 检查中心点是否在入口区域内
                        if self.zone_1 == 0:
                            if cv2.pointPolygonTest(entry_polygon, center_point, False) >= 0:
                                filtered_enter.append([bbox, score])
                                track_id = self.track_id_enter

                                # 更新鸡蛋位置和时间戳
                                if track_id not in self.egg_positions:
                                    self.egg_positions[track_id] = []
                                self.egg_positions[track_id].append((center_x, center_y, current_time))

                                # 计算速度(确保至少有两个位置记录)
                                if len(self.egg_positions[track_id]) > 1:
                                    prev_position = self.egg_positions[track_id][-2]
                                    prev_x, prev_y, prev_time = prev_position
                                    distance = np.sqrt((center_x - prev_x) ** 2 + (center_y - prev_y) ** 2)
                                    time_diff = current_time - prev_time

                                    if time_diff > 0:
                                        speed = distance / time_diff
                                        self.egg_speeds.append(speed)

这是核心跟踪部分,获取每个坤蛋的中心点,如果中心点在对应划定区域内,就把该标签结果导入到对应区域的跟踪器用于跟踪,同时更新对应坤蛋的位置和时间。当然还有计速部分,这个功能不太好使有Bug,忽略就行。

                if filtered_enter and self.zone_1 == 0:
                    tracks_enter = self.tracker_enter.update_tracks(filtered_enter, frame=frame) if filtered_enter else []
                    for track in tracks_enter:
                        if not track.is_confirmed() or track.time_since_update > 1:
                            continue
                        self.track_id_enter = track.track_id
                        if self.enter_max_ID < int(self.track_id_enter):# 更新入口区域最大ID
                            self.enter_max_ID = int(self.track_id_enter)
                        bbox_enter = track.to_tlbr()
                        cv2.rectangle(frame, (int(bbox_enter[0]), int(bbox_enter[1])), (int(bbox_enter[2]), int(bbox_enter[3])), (255, 0, 0), 2)
                        cv2.putText(frame, f'ID: {self.track_id_enter}', (int(bbox_enter[0]), int(bbox_enter[1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 0, 0), 2)                

把对应区域的跟踪结果(这里只展示入口部分)保存到filtered_enter变量中,通过self.tracker_enter.update_tracks进行跟踪,注意:每个跟踪器的跟踪函数所属的跟踪器不一样。

跟踪器输出得到的tracks_enter就是我们想要的最终跟踪结果,这里将跟踪结果提取出来用于画框和标注ID号。

这里还计算了当前该区域的最大ID号,后面计算该区域的鸡蛋数量会用到。

                # 计算流量
                elapsed_time = time.time() - self.start_time
                if elapsed_time > 0:
                    self.detect_alltime = elapsed_time - self.stop_longtime
                    self.flow_rate = self.enter_egg / self.detect_alltime # 根据入口区域的鸡蛋数量计算流量
                    
                    print(f"流量: {self.flow_rate:.2f} 个鸡蛋/秒")
                    self.time_label.setText(f'已检测时间: {self.detect_alltime:.2f} 秒')
                    self.flow_rate_label.setText(f'流量: {self.flow_rate:.2f} 个鸡蛋/秒') 

这里是计算流量的部分,通过入口区域鸡蛋总数/总检测时间得到当前流量,如果想要不同时间段的流量或者需要计算实时流量可以自行二次开发。

                # 记录当前鸡蛋数量
                
                self.enter_egg = self.enter_max_ID
                self.exit1_egg = self.exit1_max_ID
                self.exit2_egg = self.exit2_max_ID
                self.count_egg = self.enter_max_ID - self.exit1_max_ID - self.exit2_max_ID
                # 更新显示
                print(f"当前传送带中鸡蛋数量: {self.count_egg}")
                print(f"入口区域鸡蛋数量: {self.enter_egg}")
                print(f"出口1区域鸡蛋数量: {self.exit1_egg}")
                print(f"出口2区域鸡蛋数量: {self.exit2_egg}")
                self.egg_count_label.setText(f'当前传送带中鸡蛋数量: {self.count_egg}')
                self.egg_enter_label.setText(f'入口区域鸡蛋数量: {self.enter_egg}')
                self.egg_exit1_label.setText(f'出口1区域鸡蛋数量: {self.exit1_egg}')
                self.egg_exit2_label.setText(f'出口2区域鸡蛋数量: {self.exit2_egg}')
                self.display_frame(frame)  # 显示帧
                if self.zone_1 == 0:
                    self.display_frame1(frame1)  # 显示入口区域帧
                if self.zone_2 == 0:
                    self.display_frame2(frame2)  # 显示出口1区域帧
                if self.zone_3 == 0:
                    self.display_frame3(frame3)  # 显示出口2区域帧
                

            except ValueError as e:
                print(f"检测更新时发生ValueError错误: {e}")
            except Exception as e:
                print(f"检测更新时发生未知错误: {e}")

这里记录了当前各区域鸡蛋的数量,这里用出现的最大ID号来表示数量,为啥,因为需要考虑到这种情况

如果标定ID号的时候,是ID为2的鸡蛋先离开区域,ID为1的鸡蛋还在区域中,那么如果直接把track_id_enter变量,也就是ID号,算作是该区域鸡蛋数量,可能会出现计算数量为1的情况,但实际是2,因此需要以该区域最大ID号作为基准。

OK,这就是项目全部内容了,这是我算是付出时间比较多的一个小项目了,这篇博客算是我学习过程的记录。希望对大家开发项目、入门深度学习有一定的帮助。最后会放上B站视频展示链接,希望能给我的Github项目点个Star,给我的视频一个三连,谢谢!

Release egg_detection1.0 · ryhxf/egg_detection-YOLOv5-Deepsort · GitHub

基于MMyolo框架的YOLOv5+Deepsort的鸡蛋检测系统 | 多区域可选定多目标跟踪应用 代码开源_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值