我想要通过YOLOv5源代码和自己做的arduino代码实现二维云台的目标识别和自动跟踪,两部分代码分开运行都正常,但放在一起没报错也可以检测目标,但是无法跟踪

    def detect_vid(self):
        # pass
        import torch
        model = self.model
        output_size = self.output_size
        # source = self.img2predict  # file/dir/URL/glob, 0 for webcam
        imgsz = [640, 640]  # inference size (pixels)
        conf_thres = 0.25  # confidence threshold
        iou_thres = 0.45  # NMS IOU threshold
        max_det = 1000  # maximum detections per image
        # device = self.device  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        view_img = False  # show results
        save_txt = False  # save results to *.txt
        save_conf = False  # save confidences in --save-txt labels
        save_crop = False  # save cropped prediction boxes
        nosave = False  # do not save images/videos
        classes = None  # filter by class: --class 0, or --class 0 2 3
        agnostic_nms = False  # class-agnostic NMS
        augment = False  # ugmented inference
        visualize = False  # visualize features
        line_thickness = 3  # bounding box thickness (pixels)
        hide_labels = False  # hide labels
        hide_conf = False  # hide confidences
        half = False  # use FP16 half-precision inference
        dnn = False  # use OpenCV DNN for ONNX inference
        source = str(self.vid_source)
        webcam = self.webcam
        device = select_device(self.device)
        stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx
        imgsz = check_img_size(imgsz, s=stride)  # check image size
        save_img = not nosave and not source.endswith('.txt')  # save inference images
        # Dataloader
        if webcam:
            view_img = check_imshow()
            cudnn.benchmark = True  # set True to speed up constant image size inference
            dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)
            bs = len(dataset)  # batch_size
        else:
            dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
            bs = 1  # batch_size
        vid_path, vid_writer = [None] * bs, [None] * bs
        # Run inference
        if pt and device.type != 'cpu':
            #这里的 pt 和 device.type 是两个变量,它们被用来检查两个条件是否同时满足。!= 是一个不等运算符,用于判断 device.type
            # 是否不等于字符串 'cpu'。整个条件表达式的意思是:如果变量 pt 为真(非零或非空)且 device.type 的值不是 'cpu',则执
            # 行冒号后面的代码块。
            model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters())))  # warmup
            #这行代码的作用是使用模型 model 对一个零张量进行前向传播,这个张量的形状是 (1, 3, height, width),其中 height 和
            # width 是图像尺寸,*imgsz 是一个星号操作符,用于展开 imgsz 元组中的元素到位置参数。这个操作是在指定的设备 device 上
            # 进行的,并且张量的数据类型被设置为与模型参数相同的类型。这个过程通常用于模型预热(warm up),以准备后续的预测任务。
        dt, seen = [0.0, 0.0, 0.0], 0
        for path, im, im0s, vid_cap, s in dataset:
            t1 = time_sync()# 函数用于记录处理开始的时间戳
            im = torch.from_numpy(im).to(device)#将图像数据转换为 PyTorch 张量,并移动到指定的设备(CPU 或 GPU)。
            im = im.half() if half else im.float()  # uint8 to fp16/32根据变量 half 决定是否将图像数据类型转换为
            # 半精度浮点数(fp16)或单精度浮点数(fp32)。
            im /= 255  # 0 - 255 to 0.0 - 1.0将图像像素值从 0-255 范围缩放到 0.0-1.0 范围
            #for  对于  代码首先遍历名为 dataset 的数据集,其中每一项包含路径(path)、原始图像(im)、未标准化的图像(im0s)、视频捕获对象
            # (vid_cap)和字符串(s)。然后,代码执行一系列操作来准备数据进行模型推理,包括时间同步、图像转换、归一化以及批量维度的扩展。
            if len(im.shape) == 3:#这一行检查变量im的形状长度是否为3。在Python中,图像数据通常以多维数组的形式表示,其中形状长度为3
                # 意味着图像数据包含高度、宽度和通道数(通常是RGB颜色通道)。
                im = im[None]  # expand for batch dim如果图像数据的形状长度为3,这行代码将在图像数据的开头添加一个额外的轴,以便将图像
                # 转换为一个四维数组(批处理维度)。这通常是在使用深度学习模型进行批量预测之前进行的预处理步骤
            t2 = time_sync()# 函数用于记录处理开始的时间戳
            dt[0] += t2 - t1#dt是一个列表,用于累积时间差。这里,t1是上一次时间同步的时间戳。这一行代码更新dt[0]为自上次时间同步以来经过的时间。

            # Inference
            # visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
            pred = model(im, augment=augment, visualize=visualize)
            #这一行调用名为model的函数进行模型推断。im是输入图像数据,augment(如果提供)是增强参数,visualize(如果提供)控制是否可视化中间结果
            # pred变量将包含模型的输出预测。
            t3 = time_sync()#再次调用time_sync函数来记录当前时间,t3变量用于存储这个时间戳。

            dt[1] += t3 - t2#更新dt[1]为自上次时间同步以来经过的时间,即模型推断所需的时间
            # NMS
            pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
            #这一行使用非极大值抑制(Non-Maximum Suppression, NMS)算法处理模型的输出。NMS是一种用于去除重叠边界框的技术,保留最佳的边界框。
            # conf_thres, iou_thres, classes, agnostic_nms和max_det是NMS算法的参数        探测框优化
            dt[2] += time_sync() - t3#更新dt[2]为自上次时间同步以来经过的时间,即NMS处理所需的时间
            # Second-stage classifier (optional)
            # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
            # Process predictions
            #整个代码块的逻辑是测量图像输入到模型推断再到NMS处理的总时间,并将这些时间累加到相应的时间差变量中。这通常用于性能分析,以优化模型的运行时间
            for i, det in enumerate(pred):  # per image
                seen += 1
                if webcam:  # batch_size >= 1
                    p, im0, frame = path[i], im0s[i].copy(), dataset.count
                    s += f'{i}: '
                else:
                    p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
                #对于每一个检测 det,增加 seen 计数。
#如果 webcam 为真(即处理实时视频流),则将当前图像路径 path[i]、原始图像副本 im0s[i] 以及当前帧号 dataset.count 分别赋值给 p、im0 和 frame。
#如果 webcam 为假(即处理静态图片集),则将所有图像路径 path 作为一个列表、原始图像副本 im0s 作为一个副本以及当前帧号 getattr(dataset, 'frame',
# 0) 赋值给 p、im0 和 frame。将路径转换为 Path 对象以便后续操作
                p = Path(p)  # to Path
                # save_path = str(save_dir / p.name)  # im.jpg
                # txt_path = str(save_dir / 'labels' / p.stem) + (
                #     '' if dataset.mode == 'image' else f'_{frame}')  # im.txt
                s += '%gx%g ' % im.shape[2:]  # print string
                gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
                imc = im0.copy() if save_crop else im0  # for save_crop
                annotator = Annotator(im0, line_width=line_thickness, example=str(names))
                #根据是否保存裁剪图像,决定是否复制 im0 到 imc。创建 Annotator 实例,用于在图像上绘制边界框和标签
                # ,其中 line_width 设置线条宽度,example 设置示例字符串
                #代码片段是一个循环结构,用于处理预测结果 pred 中的每个检测(detection)。变量 seen 用于计数已经处理过的图像数量,
                # webcam 是一个布尔标志,用来区分是否使用摄像头实时输入数据(batch size 大于等于 1)还是处理预先存储的图片集
                # (batch size 小于 1)。
                if len(det):#if len(det): 这一行是一个条件语句,它检查 det 变量的长度是否非零。如果 det 是非空的,即至少
                    # 包含一个检测结果,那么接下来的代码块将被执行。


                    # Rescale boxes from img_size to im0 size
                    det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()


                    # 现在rotated_rects包含转换后的RotatedRect对象
                    #det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() 这行代码使用 scale_coords
                    # 函数将检测框的坐标从模型输入图像的尺寸 im.shape[2:] 缩放到实际图像的尺寸 im0.shape。.round() 方法用于将缩放
                    # 后的浮点数坐标四舍五入到最近的整数

                    #在代码中,det 是一个二维NumPy数组,包含了每个检测结果的信息。det[:, :4] 表示选择 det 数组中所有行的前四列。
                    # 这些列通常代表检测框的坐标,按照 [x1, y1, x2, y2] 的顺序排列,其中 (x1, y1) 和 (x2, y2) 分别是边界框
                    # 左上角和右下角的坐标点。

                    #在执行推理后,原始的检测框坐标可能是相对于模型输入图像的大小定义的,而 scale_coords 函数用于将这些坐标缩放到
                    # 实际图像的大小。这样做是为了确保检测框在图像上正确显示。最后,.round() 方法用于将缩放后的浮点数坐标四舍五入
                    # 到最近的整数,以便于绘制和计算。

                    # Convert xyxy coordinates to corner points

                    # Print results
                    for c in det[:, -1].unique():#这行代码遍历det数组的最后一列(索引为-1),.unique()函数返回该列中非
                        # 重复元素的列表。因此,循环变量c代表det数组最后一列中的唯一类别值
                        n = (det[:, -1] == c).sum()  # detections per class
                        #对于当前迭代的类别c,(det[:, -1] == c)创建一个布尔数组,其中True表示det数组最后一列的值等于c。
                        #然后.sum()计算这个布尔数组中True的数量,即属于该类别的检测数量
                        s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string
                        #这里,f-string用于格式化字符串,包含当前类别的检测数量n和通过类索引int(c)从names列表中获取的类名。
                        # 如果检测数量大于1,则在类名后面加上复数形式的's'。最后,这个字符串被追加到变量s上,该行尾的逗号和空格用于在打印时分隔不同的类别信息。

                        #for c in det[:, -1].unique(): ... 循环遍历 det 数组中最后一列的唯一值,这一列通常包含类别标签。
                        # 对于每个独特的类别,计算该类别的检测数量,并将这些信息添加到字符串 s 中      探测到的类组

                    # Write results
                    for *xyxy, conf, cls in reversed(det):

                        #reversed(det)通常用于Python编程语言中,它是一个内置函数,用于反转一个可迭代对象(如列表、字符串等)。在循环结构中,
                        # reversed(det)的作用是让循环按照相反的顺序遍历集合元素。

                        # 这一行代码表示对det这个迭代器进行反向遍历,其中*xyxy代表一个变量接收
                        # 多个值,conf和cls分别代表置信度和类别标签。每次迭代都会取出一个检测结果,包括边界框坐标、置信度和类别标签
                        if save_txt:  # Write to file,这是一个条件语句,用于检查变量save_txt是否为真。如果为真,则执行后续的文件写入操作。
                            xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(
                                -1).tolist()  # normalized xywh
                            #这行代码首先将边界框坐标转换为PyTorch张量,并调整形状为(1, 4),然后调用函数xyxy2xywh将其转换为标准的
                            # (xmin, ymin, xmax, ymax)格式,接着除以缩放因子gn进行归一化处理,最后通过.tolist()方法将张量转换为列表形式。



                            line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                            #这行代码根据save_conf变量的值决定是否包含置信度信息在内的输出列表。如果save_conf为真,则输出包含类标签、归一化
                            #边界框坐标和置信度的列表;如果为假,则只输出类标签和归一化边界框坐标的列表。

                            #综上所述,代码的逻辑是遍历检测结果,对于每个检测结果,根据是否需要保存文本文件来决定是否将其以特定格式写入文件。
                            # 如果需要保存,还会包括置信度信息。

                            # with open(txt_path + '.txt', 'a') as f:
                              #f.write(('%g ' * len(line)).rstrip() % line + '\n')

                        if save_img or save_crop or view_img:  # Add bbox to image
                            #代码片段中的条件逻辑是一个简单的逻辑判断语句,用于检查变量 save_img、save_crop 和 view_img 中是否至少有一个为真(True)。
                            # 这里的 or 关键字表示逻辑“或”操作,意味着只要其中任意一个变量为真,整个条件表达式的结果就是真。如果这些变量都为假(False),
                            # 则条件表达式的结果为假。save_img: 如果设置为真,表示需要保存图像。
                            # save_crop: 如果设置为真,表示需要保存裁剪后的图像部分。
                            # view_img: 如果设置为真,表示需要显示图像。
                            # 这个条件判断是为了决定是否执行添加边界框(bounding box)和标签到图像的操作。如果上述任一变量为真,程序将继续执行注释中提到的操作
                            # ,即在图像上绘制边界框并附加相应的标签。
                            c = int(cls)  # integer class
                            label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                            annotator.box_label(xyxy, label, color=colors(c, True))

                            #代码中的 int(cls) 将类(class)转换为整数类型,这通常是因为在某些情况下,类别标签需要作为整数来使用。names[c] 可能是一个包含类
                            # 别名称的数组或字典,用于获取对应类别的名称。hide_labels 和 hide_conf 似乎是布尔标志,控制是否隐藏类别标签和置信度分数。
                            # annotator.box_label() 函数负责在图像上绘制边界框和附加标签,colors(c, True) 函数用于根据类别 c 返回颜色

                            # if save_crop:
                            #     save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg',
                            #                  BGR=True)
                            #for *xyxy, conf, cls in reversed(det): ... 这个循环遍历 det 数组,并将检测框坐标、置信度和类别标签
                            # 以反向顺序处理。如果设置了保存文本文件的标志 save_txt,则将检测结果以特定格式写入文本文件。如果设置了将边界
                            # 框添加到图像的标志 save_img 或其他相关标志,则在图像上绘制边界框。


                # Print time (inference-only)
                LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
                #这行代码使用了Python的logging模块来输出一条信息级别的日志。LOGGER是一个已经定义好的logger对象,info方法用于记录信息级别的日志。
                # f-string是Python 3.6引入的字符串插值方式,{s}和{t3 - t2:.3f}是f-string中的表达式,分别代表变量s的值和计算变量t3减去t2后
                # 保留三位小数的结果。这行代码的作用是记录某个操作完成并显示所用时间

                # Stream results接下来的代码块是用于实时显示结果的
                # Save results (image with detections)接下来的代码块是用于保存带有检测结果的图像
                im0 = annotator.result()#这里创建了一个变量im0,其值为annotator对象调用result方法的返回值。annotator是
                # 处理图像并进行探测框建立的类实例
                frame = im0#将annotator.result()的结果赋值给变量frame,这里frame代表原始的图像数据
                resize_scale = output_size / frame.shape[0]#算缩放比例resize_scale,output_size是期望的图像高度或宽度
                # (根据上下文可能是高度或宽度),frame.shape[0]是原始图像的高度。
                frame_resized = cv2.resize(frame, (0, 0), fx=resize_scale, fy=resize_scale)
                #使用OpenCV库的cv2.resize函数将frame按照计算出的缩放比例resize_scale进行缩放,fx和fy参数设置为相同的值以保持图像的宽高比不变
                cv2.imwrite("images/tmp/single_result_vid.jpg", frame_resized)#将缩放后的图像保存到指定路径的文件中
                self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg"))
                #最后,这行代码将刚才保存的图像设置为图形界面上名为vid_img的组件的像素图,使得图像能够在图形界面上显示出来。QPixmap是Qt框架中用于
                # 表示图像的类。

                # self.vid_img
                # if view_img:
                # cv2.imshow(str(p), im0)
                # self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg"))
                # cv2.waitKey(1)  # 1 millisecond


            if cv2.waitKey(25) & self.stopEvent.is_set() == True:#是OpenCV中的一个函数,用于等待键盘输入,参数25表示等待时间为25毫秒。
                # 如果在这段时间内有按键动作,该函数返回按键的ASCII码值;如果没有按键动作,则返回-1。
                # self.stopEvent.is_set()是检查一个线程事件对象是否被设置为True状态。这里的self.stopEvent应该是类的一个属性,
                # 通常用于控制循环或者线程的停止条件。
                #& 运算符在这里用于按位与操作,确保只有当两个条件都为True时,整个表达式的结果才为True。
                self.stopEvent.clear()
                #如果上述条件为True,执行此语句,将清除之前设置的停止事件,允许程序继续执行后续代码。
                self.webcam_detection_btn.setEnabled(True)#重新启用名为webcam_detection_btn的按钮控件,使其可以再次被用户点击。
                self.mp4_detection_btn.setEnabled(True)#同样,重新启用名为mp4_detection_btn的按钮控件
                self.reset_vid()#调用reset_vid方法,这个方法可能是用来重置视频检测相关的状态或变量
                break#退出当前的循环或者中断当前的执行流程。
#这段代码通常用于图像处理或视频分析的程序中,用于响应用户输入来控制程序的运行状态,例如停止视频捕捉或处理,并重置相关的检测按钮和视频状态。
        # self.reset_vid()
        rotated_rects = []  # 创建一个空列表来存储RotatedRect对象
        for i in range(len(det)):
            x, y, w, h = det[:, :4]
            center = (float(x), float(y))
            size = (float(w), float(h))
            angle = 0
            rotated_rect = cv2.RotatedRect(center, size, angle)
            rotated_rects.append(rotated_rect)  # 将RotatedRect对象添加到列表中
        camera = cv2.VideoCapture(0)

        def distance_to_camera(knownWidth, focalLength, perWidth):
            return (knownWidth * focalLength) / perWidth

        KNOWN_DISTANCE = 30
        KNOWN_WIDTH = 14.5
        KNOWN_HEIGHT = 12

        def calculate_focalDistance(Img_path):
            global focalLength  # global定义变量focallength焦距
            image = cv2.imread(Img_path)
            marker = rotated_rect  # 返回cv2.minAreaRect(c)的数据
            focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH  # 得到焦距

            print('focalLength = ', focalLength)

        # connectWifi()#先建立连接
        camera = cv2.VideoCapture(0)
        calculate_focalDistance("D:\picture\\red4.jpg")

        # Arduino端口
        serialPort = "COM3"
        baudRate = 9600
        ser = serial.Serial(serialPort, baudRate, timeout=0.5)

        # 判断在哪个象限
        def JudgeQuadrant(axis_x, axis_y):
            distanceX = axis_x - 320;
            distanceY = axis_y - 240;
            if (distanceX < 0 and distanceY < 0):
                return 2
            if (distanceX > 0 and distanceY < 0):
                return 1
            if (distanceX < 0 and distanceY > 0):
                return 3
            if (distanceX > 0 and distanceY > 0):
                return 4

        #  返回与x轴的夹角
        def computeAngle(axis_x, axis_y):
            dx = abs(axis_x - 320);
            dy = abs(axis_y - 240)
            if dx == 0:
                return
            tanA = dy / dx;
            angle1 = math.atan(tanA)
            return math.degrees(angle1)

        while camera.isOpened():  # 摄像头打开条件下

            (grabbed, frame) = camera.read()
            # camera.read()开启电脑默认摄像头,参数grabbed为True 或者False,代表有没有读取到图片 第二个参数frame表示截取到一帧的图片

            marker = rotated_rects
            # 对这一帧图片建框
            print(marker)
            if marker == 0:  # 无检测成果
                # cv2.imshow("captureR", frame)
                # cv2.destroyWindow("captureR")
                # sendMsg("dis:%.0f" % (99999))
                continue
            inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])
            # inches英寸
            #  sendMsg("dis:%.0f" % (inches))
            box = cv2.boxPoints(marker)
            # 获取矩形四个顶点,浮点型
            box = np.int64(box)
            # 储存为64位整数

            # 画出轮廓
            cv2.drawContours(frame, [box], -1, (0, 255, 0), 2)
            # 在图像中某一位置显示文字
            cv2.putText(frame, "%.2fcm" % (inches),
                        (frame.shape[1] - 600, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,
                        2.0, (0, 255, 0), 3)
            # cv2.destroyWindow("captureR")
            # 输出图像,并将图像命名为frame
            cv2.imshow("capture", frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            # camera.release()
            cv2.destroyWindow("capture")
            # client.close()
            # (grabbed, frame) = camera.read()
            #  marker = find_marker(frame)  # 获取物体轮廓,具体参考我的第一篇博客

            #    continue
            box = cv2.boxPoints(marker)
            # 获取矩形四个顶点,浮点型
            box = np.int0(box)
            # 取整
            # 此轮廓中心  marker[0][0]为 x轴  marker[0][1]为y轴
            JudgeQuadrant(marker[0][0], marker[0][1])
            # 象限
            Quadrant = JudgeQuadrant(marker[0][0], marker[0][1]);
            # 角度
            if computeAngle(marker[0][0], marker[0][1]) == None:
                continue;
            Angle = int(computeAngle(marker[0][0], marker[0][1]));
            # 下面的舵机,如果与x轴的角度相差超过20度,则根据所在象限控制左右转动
            if 90 - Angle > 10:
                Quadrant = JudgeQuadrant(marker[0][0], marker[0][1])
                ser.write(str.encode(str(Quadrant))
                          )
            # 上面的舵机
            Quadrant = JudgeQuadrant(marker[0][0], marker[0][1])
            if Angle > 10 and (Quadrant == 1 or Quadrant == 2):
                # 若是在1、2象限则控制上面的舵机向下转动
                ser.write(str.encode(str(6)))
            elif Angle > 10 and (Quadrant == 3 or Quadrant == 4):
                # 若是在3、4象限则控制上面的舵机向上转动
                ser.write(str.encode(str(5)))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值