> 技术文档 > 用opencv和caixcam进行矩形框识别中点坐标输出,以及激光识别和激光坐标输出。#电赛E题_识别矩形并获取四个点的坐标

用opencv和caixcam进行矩形框识别中点坐标输出,以及激光识别和激光坐标输出。#电赛E题_识别矩形并获取四个点的坐标

主包比较懒不想写太多。

在嵌入式系统(如 MaixCAM)中,如何高效地识别图像中的特征,并将这些数据实时传输到主控板或机器人控制器,是实际应用中最关键的一环。本文将通过一个红点识别 + 矩形检测 + 串口传输的完整案例,解析背后的原理与实现方法。

适用场景如:

  • 棋盘识别(三子棋、定位标靶等)

  • 激光点跟踪系统

  • 机器人对位视觉引导

  • 人机交互识别与图像辅助控制

实现效果:

先将识别到的四个角点传出,传出后不再识别(因为是固定的)。

随后识别红激光。实时传输激光位置。

矩形检测与中点计算

目标:找到两个矩形之间的对应角点,并计算四个中点(例如:棋盘格或坐标定位板)

关键步骤

  • 轮廓提取:使用 adaptiveThreshold + morphologyEx 组合增强二值图像

  • 多边形拟合cv2.approxPolyDP 找到4点的凸四边形

  • 矩形判断:角度接近90°、边界不越界、面积合理

  • 重复矩形去重:使用 is_similar_rect 判定是否为“同一个矩形”

  • 角点匹配:使用欧几里得距离最短匹配两个矩形角点之间的对应关系

最终计算两个矩形的 4 个中点位置。

系统通过串口向主控发送如下格式的数据包:

| AA 55 | 红点X | 红点Y | 中点1X | 中点1Y | ... | 中点4X | 中点4Y |

payload = b\'\\xAA\\x55\'payload += pack(\"<hh\", *red_dot)for x, y in midpoints:    payload += pack(\"<hh\", x, y)serial.write(payload)

发送格式采用小端字节序(<h),方便主控直接反序列化读取。

完整代码如下

from maix import image, camera, display, app, uart, timeimport cv2import numpy as npfrom struct import pack# 初始化串口serial = uart.UART(\"/dev/ttyS0\", 115200)# 初始化摄像头和显示器cam = camera.Camera(320, 240, fps=80)disp = display.Display()#红色在 RGB 空间容易受光照干扰,而在 LAB 色彩空间的 A 通道 中,红色数值范围更集中。# 红点检测阈值(LAB 色彩空间 A 通道)A_MIN = 150A_MAX = 255kernel_red = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))MIN_AREA = 2000MAX_AREA = 80000pause_rect_detect = False  # 是否暂停矩形检测def sort_rect_points(pts):    pts = sorted(pts, key=lambda p: (p[1], p[0]))    top = sorted(pts[:2], key=lambda p: p[0])    bottom = sorted(pts[2:], key=lambda p: p[0], reverse=True)    return [top[0], top[1], bottom[0], bottom[1]]def match_corners_by_distance(ref_pts, target_pts):    matched = [None] * 4    used = [False] * 4    for i, p1 in enumerate(ref_pts):        min_dist = float(\"inf\")        min_j = -1        for j, p2 in enumerate(target_pts):            if used[j]:                continue            dist = np.linalg.norm(np.array(p1) - np.array(p2))            if dist < min_dist:                min_dist = dist                min_j = j        matched[i] = target_pts[min_j]        used[min_j] = True    return matcheddef is_similar_rect(rect1, rect2, threshold=8, area_thresh=0.05):    try:        rect1 = sort_rect_points(rect1)        rect2 = sort_rect_points(rect2)        avg_dist = np.mean([np.linalg.norm(np.array(p1) - np.array(p2)) for p1, p2 in zip(rect1, rect2)])        area1 = cv2.contourArea(np.array(rect1, dtype=np.int32))        area2 = cv2.contourArea(np.array(rect2, dtype=np.int32))        area_diff_ratio = abs(area1 - area2) / max(area1, area2)        return avg_dist < threshold and area_diff_ratio < area_thresh    except Exception as e:        print(\"矩形比较异常:\", e)        return Falsedef is_rectangle(approx):    if approx is None or len(approx) != 4 or not cv2.isContourConvex(approx):        return False    pts = [point[0] for point in approx]    def angle(p1, p2, p3):        v1 = np.array(p1) - np.array(p2)        v2 = np.array(p3) - np.array(p2)        norm1 = np.linalg.norm(v1)        norm2 = np.linalg.norm(v2)        if norm1 == 0 or norm2 == 0:            return 0        cos_angle = np.clip(np.dot(v1, v2) / (norm1 * norm2), -1.0, 1.0)        return np.arccos(cos_angle) * 180 / np.pi    angles = [angle(pts[i - 1], pts[i], pts[(i + 1) % 4]) for i in range(4)]    return all(80 < ang  20:                    M = cv2.moments(max_cnt)                    if M[\"m00\"] != 0:                        cx = int(M[\"m10\"] / M[\"m00\"])                        cy = int(M[\"m01\"] / M[\"m00\"])                        red_dot = (cx, cy)                        cv2.circle(img_raw, (cx, cy), 5, (0, 255, 0), -1)        except Exception as e:            print(\"红点检测异常:\", e)        # 矩形检测与中点计算        if not pause_rect_detect:            try:                gray = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY)                bin_img = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,                                                cv2.THRESH_BINARY, 11, 2)                closed = cv2.morphologyEx(bin_img, cv2.MORPH_CLOSE,                                           cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))                contours, _ = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)                rectangles = []                for contour in contours:                    area = cv2.contourArea(contour)                    if not (MIN_AREA <= area <= MAX_AREA):                        continue                    x, y, w, h = cv2.boundingRect(contour)                    margin = 1  # 可调边缘安全距离(像素)                    if x < margin or y  img_raw.shape[1] - margin or y + h > img_raw.shape[0] - margin:                            continue                    approx = cv2.approxPolyDP(contour, 0.02 * cv2.arcLength(contour, True), True)                    if is_rectangle(approx):                        rect = [tuple(pt[0]) for pt in approx]                        if not any(is_similar_rect(rect, r) for r in rectangles):                            rectangles.append(rect)                            cv2.drawContours(img_raw, [np.array(rect, dtype=np.int32)], -1, (0, 255, 0), 2)                            for x, y in rect:                                cv2.circle(img_raw, (x, y), 5, (0, 0, 255), -1)                if len(rectangles) == 2:                    r1 = sort_rect_points(rectangles[0])                    r2_unsorted = sort_rect_points(rectangles[1])                    r2 = match_corners_by_distance(r1, r2_unsorted)                    for i in range(4):                        mid = ((r1[i][0] + r2[i][0]) // 2, (r1[i][1] + r2[i][1]) // 2)                        midpoints[i] = mid                        cv2.line(img_raw, r1[i], r2[i], (255, 0, 255), 1)                        cv2.circle(img_raw, mid, 3, (0, 255, 255), -1)            except Exception as e:                print(\"矩形检测异常:\", e)        # 数据打包并发送        try:            payload = b\'\\xAA\\x55\'            if red_dot[0] < 0 or red_dot[1] < 0:                payload += pack(\"<hh\", 0, 0)            else:                payload += pack(\"<hh\", *red_dot)            for x, y in midpoints:                if x < 0 or y < 0:                    payload += pack(\"<hh\", 0, 0)                else:                    payload += pack(\"<hh\", x, y)            serial.write(payload)        except Exception as e:            print(\"串口发送异常:\", e)        # 显示图像        try:            img_show = image.cv2image(img_raw, copy=False)            disp.show(img_show)        except Exception as e:            print(\"图像显示失败:\", e)        time.sleep_ms(1)    except Exception as e:        print(\"主循环异常:\", e)