目录

1. 项目背景

2. 系统架构

3. 关键技术详解

3.1 矩形检测

3.2 激光点检测

3.3 坐标到角度的映射

3.4 PID控制器

3.5 舵机角度到PWM的转换

3.6 串口通信

4. 校准流程

4.1 手动校准

4.2 自动校准

5. 运行与调试

5.1 环境配置

5.2 串口设置

5.3 运行程序

5.4 调试技巧

6. 总结与扩展

扩展方向

关于完整代码


论文投稿:
第三届机器学习与神经网络国际学术会议
大会官网:https://ais.cn/u/neQNFf
大会时间:2026年3月27-29日
大会地点:中国-四川-成都


 

1. 项目背景

在机器人或自动化项目中,经常需要利用摄像头捕捉目标位置,并控制舵机指向该位置。本文介绍一个完整的解决方案:通过摄像头检测矩形区域和红色激光点,建立图像坐标与舵机角度的映射关系,最终通过串口发送PWM信号控制舵机。系统采用PID算法辅助校准,支持自动/手动添加校准点,实现闭环控制。

适用场景:激光瞄准、目标跟踪、云台控制、机器人视觉引导等。

2. 系统架构

整个系统分为三个核心模块:

  • 视觉检测模块:利用OpenCV检测矩形区域(作为参考目标)和红色激光点。

  • 坐标映射模块:根据校准点建立图像坐标到舵机角度的映射(采用插值算法)。

  • 舵机控制模块:将计算出的角度转换为PWM值并通过串口发送给舵机。

模块间关系如图:

3. 关键技术详解

3.1 矩形检测

矩形检测用于提供校准参考点(四个顶点和中心点)。算法步骤:

  1. 预处理:转灰度图、高斯模糊。

  2. 边缘检测:采用自适应Canny阈值,避免固定阈值在不同光照下失效。

    • 原理:取灰度中值,下阈值 = max(0, (1-σ)×中值),上阈值 = min(255, (1+σ)×中值)

  3. 形态学闭运算:连接断裂边缘。

  4. 轮廓查找:使用cv2.findContours获取所有外轮廓。

  5. 矩形筛选

    • 轮廓面积大于阈值(如1500像素)

    • 近似多边形顶点数为4(cv2.approxPolyDP

    • 凸度检查:轮廓面积 / 凸包面积 > 0.85(防止凹陷形状)

    • 长宽比限制:最大边长/最小边长 < 5.0

    • 凸包面积与轮廓面积比 < 1.2(避免凸包过大)

  6. 亚像素精修:对筛选出的角点使用cornerSubPix提高精度。

关键代码片段

def find_best_rect(frame_bgr):
    gray = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY)
    edges = auto_canny(gray)               # 自适应Canny
    contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, ...)
    for cnt in contours:
        rect = is_good_rect(cnt, RECT_APPROX_EPS)  # 筛选矩形
        if rect is not None:
            best = rect
    # 亚像素精修
    best = cv2.cornerSubPix(gray_i, np.float32(best), ...)
    return best

3.2 激光点检测

红色激光点在HSV色彩空间中具有明显的色调特征。采用双阈值范围(0-10和170-180)提取红色区域:

RED_HSV_LOW1 = np.array([0, 80, 35])
RED_HSV_HIGH1 = np.array([10, 255, 255])
RED_HSV_LOW2 = np.array([170, 80, 35])
RED_HSV_HIGH2 = np.array([180, 255, 255])

通过形态学开闭运算去除噪声,然后找到最大轮廓的中心点作为激光点坐标。
详细的激光点识别部分实现方法可以看看博主的另一篇关于激光的文章,那里有详解哦。

关键代码片段

def detect_laser_point(frame):
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    mask1 = cv2.inRange(hsv, RED_HSV_LOW1, RED_HSV_HIGH1)
    mask2 = cv2.inRange(hsv, RED_HSV_LOW2, RED_HSV_HIGH2)
    mask = cv2.bitwise_or(mask1, mask2)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    contours, _ = cv2.findContours(mask, ...)
    if contours:
        c = max(contours, key=cv2.contourArea)
        (x, y), r = cv2.minEnclosingCircle(c)
        return (int(x), int(y))
    return None

3.3 坐标到角度的映射

采用散点插值法建立图像坐标 (x, y) 到舵机角度 (θ_h, θ_v) 的映射。校准点集合存储为字典,键为坐标元组,值为角度元组。对于任意查询点,使用scipy.interpolate.griddata进行线性插值;若查询点在校准范围外则使用最近邻插值。

关键代码片段

def get_servo_angles(self, x, y):
    # 如果是校准点直接返回精确值
    if (round(x,2), round(y,2)) in self.calibration_points:
        return self.calibration_points[(round(x,2), round(y,2))]
    # 否则插值
    theta_h, theta_v = griddata(
        np.array(list(self.calibration_points.keys())),
        np.array(list(self.calibration_points.values())),
        (x, y), method='linear'  # 或 'nearest'
    )
    return (theta_h, theta_v)

3.4 PID控制器

在自动校准过程中,需要将激光点移动到目标校准点。PID控制器根据当前位置与目标位置的误差计算出需要的舵机角度变化,从而实现平滑追踪。

公式

输出 = Kp * 误差 + Ki * 误差积分 + Kd * 误差微分

关键代码片段

class PIDController:
    def calculate_angles(self, target_x, target_y, current_x, current_y):
        error_x = target_x - current_x
        error_y = target_y - current_y
        self.error_sum_h += error_x
        self.error_sum_v += error_y
        d_error_h = error_x - self.last_error_h
        d_error_v = error_y - self.last_error_v
        theta_h = self.kp * error_x + self.ki * self.error_sum_h + self.kd * d_error_h
        theta_v = self.kp * error_y + self.ki * self.error_sum_v + self.kd * d_error_v
        # 角度限幅
        theta_h = max(-90, min(90, theta_h))
        theta_v = max(-90, min(90, theta_v))
        return (theta_h, theta_v)

3.5 舵机角度到PWM的转换

舵机通常通过PWM信号控制,不同角度对应不同的脉冲宽度。本项目中舵机中值对应PWM=1566,每度变化对应DEGREE_UNIT=11个PWM单位,且符合“左大右小,上大下小”的映射关系:

  • 水平角度 θ_h:正值表示左转,PWM = 中值 - θ_h × DEGREE_UNIT

  • 垂直角度 θ_v:正值表示上转,PWM = 中值 + θ_v × DEGREE_UNIT

PWM范围限制在1000~2000之间,避免损坏舵机。

关键代码片段

def send_servo_data(self, angle_h, angle_v):
    pwm_h = SERVO_MID - angle_h * DEGREE_UNIT
    pwm_v = SERVO_MID + angle_v * DEGREE_UNIT
    pwm_h = max(SERVO_MIN, min(SERVO_MAX, pwm_h))
    pwm_v = max(SERVO_MIN, min(SERVO_MAX, pwm_v))
    if self.serial_port:
        cmd = f"{int(pwm_h)},{int(pwm_v)}\n"
        self.serial_port.write(cmd.encode())

3.6 串口通信

使用pyserial库与下位机通信,发送格式为"水平PWM,垂直PWM\n"。代码中包含了异常处理和模拟模式(当串口不可用时打印模拟信息)。

4. 校准流程

系统需要先建立坐标映射关系,否则无法输出角度。校准有两种方式:

4.1 手动校准

  1. 启动程序,将舵机置于初始位置。

  2. 观察摄像头画面,将激光点对准矩形某个顶点(例如左上角)。

  3. 按 m 键,在终端输入当前舵机的水平、垂直角度(可通过串口调试工具获取或已知值)。

  4. 重复以上步骤,添加矩形四个顶点和中心点(至少4个点)。

  5. 此后,激光点移动到任意位置,程序会自动计算并发送角度。

4.2 自动校准

  1. 确保摄像头能稳定检测到矩形(绿色轮廓和蓝色中心点)。

  2. 按 c 键,程序将依次尝试移动到矩形的四个顶点和中心。

  3. 每次移动到目标点后,PID控制器会计算需要的角度并发送给舵机(实际需配合闭环反馈,本例中仅模拟了计算过程,实际应用中需要不断调整直到误差小于阈值)。

  4. 所有点校准完成后,映射器建立插值关系。

注意:自动校准需要舵机具备闭环反馈或多次迭代调整。若要完整实现,应在循环中反复读取激光位置并调整舵机,直到到达目标。

5. 运行与调试

5.1 环境配置

  • Python 3.7+

  • 依赖库:opencv-pythonnumpyscipypyseriallogging

安装命令:

pip install opencv-python numpy scipy pyserial

5.2 串口设置

根据实际硬件修改串口设备(Linux下如/dev/ttyUSB0,Windows下为COM3),并确保波特率(115200)等参数与下位机一致。

5.3 运行程序

将上述关键代码整合,编写主循环。主循环中实时检测矩形和激光点,并响应键盘命令:

  • 按 c:开始自动校准(需先检测到矩形)。

  • 按 m:手动添加当前激光点作为校准点(需输入当前舵机角度)。

  • 按 a:查询当前激光点对应的舵机角度。

  • u/j/i/k:调节HSV阈值参数。

5.4 调试技巧

  • 矩形检测:若矩形无法检测,可调整RECT_MIN_AREARECT_APPROX_EPS等参数,或改善光照条件。

  • 激光点检测:使用u/j/i/k实时调节HSV阈值,使红色激光点能被正确掩膜。

  • 串口通信:若发送失败,检查串口权限(Linux下需sudo或添加用户到dialout组)。

6. 总结与扩展

本文实现了一个完整的基于视觉的舵机控制系统,涵盖了图像处理、坐标映射、PID控制、串口通信等关键环节。通过校准点插值,可以灵活地将任意图像坐标转换为舵机角度,适用于跟踪、瞄准等应用。

扩展方向

  1. 多目标跟踪:结合YOLO等目标检测算法,自动跟踪多个目标。

  2. 深度学习优化:使用神经网络直接预测角度,取代插值方法。

  3. 无线通信:将串口替换为Wi-Fi/蓝牙,实现远程控制。

  4. 实时性能优化:降低分辨率、使用C++实现,提高帧率。

关于完整代码

本文提供了核心算法原理和关键代码片段,完整的可运行程序需要读者根据自身硬件和需求进行整合。作者已在对话中给出了完整的Python实现,读者可参考该实现进行修改和移植。如有疑问,欢迎在评论区交流!


希望这篇笔记能帮助您快速上手类似的视觉控制项目。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐