引言

在工业检测、机器人视觉、智能分拣等应用场景中,我们经常需要实时检测特定颜色和形状的物体。例如:

  • 冰壶比赛自动计分系统:检测冰面上的圆形冰壶
  • 工业零件分拣:检测红色圆形螺丝、蓝色方形螺母
  • 自动驾驶交通标志识别:检测圆形红圈禁令标志
  • AGV 小车导航:识别地面彩色圆形二维码

本文将从简单到复杂,介绍几种常见的实现方案,对比它们的性能,并提供完整的开源参考代码,帮助你根据实际场景选择最合适的方案。

方案对比总览

我们主要对比四种主流方案:

方案 原理 计算量 准确率 适合场景 MCU 能否运行
颜色分割 + 轮廓检测 阈值分割 + 形状分析 极低 对颜色形状变化敏感 背景简单、光照稳定 ✅ Cortex-M7 可以
颜色空间转换 + Hough 变换 Hough 圆/直线检测 圆形检测较好 固定形状检测 ✅ Cortex-M4 可以
Blob 分析 + 特征匹配 连通域分析 + 形状分类 中等 多目标批量处理 ✅ Cortex-M7 可以
深度学习目标检测 YOLO/SSD 直接检测 鲁棒性强 复杂背景、光照变化 ❌ 需要 MCU+NPU 或 Linux

下面详细介绍每种方案的实现。

方案一:颜色分割 + 轮廓检测

1.1 算法流程

原始图像 RGB/BGR 颜色空间转换 RGB → HSV 颜色阈值分割 二值掩码 形态学处理 腐蚀 + 膨胀 查找轮廓 cv2.findContours 形状特征计算 面积、周长、圆形度 输出:符合颜色和形状要求的目标
颜色分割 + 轮廓检测流程图

1.2 核心原理

  1. 颜色空间转换:从 RGB 转到 HSV 颜色空间,更容易按颜色分割
  2. 阈值分割:对 H/S/V 三个通道设置范围,得到二值掩码
  3. 形态学处理:腐蚀 + 膨胀去除噪声
  4. 轮廓查找:找到所有连通区域
  5. 形状特征计算:计算面积、周长、圆形度、矩形度等特征
  6. 特征匹配:筛选符合指定形状的目标

1.3 完整 Python 实现

import cv2
import numpy as np

def detect_color_shape(
    image, 
    color_lower=np.array([0, 120, 70]), 
    color_upper=np.array([10, 255, 255]),
    shape_type="circle",
    min_area=100,
    max_area=10000,
    circularity_threshold=0.8,
    aspect_ratio_range=(0.9, 1.1)
):
    """
    检测指定颜色和形状的物体
    
    参数:
        image: 输入 RGB/BGR 图像
        color_lower: HSV 颜色下限
        color_upper: HSV 颜色上限
        shape_type: "circle" / "square" / "rectangle"
        min_area: 最小面积(像素)
        max_area: 最大面积(像素)
        circularity_threshold: 圆形度阈值(0~1,越大越圆)
        aspect_ratio_range: 宽高比范围(方形接近 1)
    
    返回:
        detections: 检测结果列表 [(x, y, w, h, contour), ...]
        mask: 颜色分割掩码(用于调试)
    """
    # 1. 颜色空间转换:BGR → HSV
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    
    # 2. 颜色阈值分割
    mask = cv2.inRange(hsv, color_lower, color_upper)
    
    # 3. 形态学处理去除噪声
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    
    # 4. 查找轮廓
    contours, hierarchy = cv2.findContours(
        mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
    )
    
    detections = []
    
    for contour in contours:
        # 计算轮廓面积,过滤太小/太大的
        area = cv2.contourArea(contour)
        if area < min_area or area > max_area:
            continue
        
        # 计算轮廓周长
        perimeter = cv2.arcLength(contour, True)
        
        # 多边形逼近
        approx = cv2.approxPolyDP(contour, 0.04 * perimeter, True)
        
        # 获取外接矩形
        x, y, w, h = cv2.boundingRect(contour)
        
        # 形状判断
        matched = False
        
        if shape_type == "circle":
            # 圆形度 = 4π * 面积 / (周长^2)
            # 完美圆形 = 1,越不规则值越小
            circularity = 4 * np.pi * area / (perimeter * perimeter)
            if circularity >= circularity_threshold:
                matched = True
        
        elif shape_type == "square":
            # 宽高比接近 1,且顶点数约为 4
            aspect_ratio = float(w) / h
            if (len(approx) == 4 and 
                aspect_ratio >= aspect_ratio_range[0] and 
                aspect_ratio <= aspect_ratio_range[1]):
                matched = True
        
        elif shape_type == "rectangle":
            # 顶点数约为 4 即可
            if len(approx) == 4:
                matched = True
        
        elif shape_type == "triangle":
            if len(approx) == 3:
                matched = True
        
        if matched:
            center_x = x + w // 2
            center_y = y + h // 2
            detections.append((center_x, center_y, w, h, contour))
    
    return detections, mask

1.4 使用示例

# 检测红色圆形物体
# HSV 红色范围(两种分段,因为红色在 H 通道首尾)
lower_red1 = np.array([0, 120, 70])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 120, 70])
upper_red2 = np.array([180, 255, 255])

# 读取图像
image = cv2.imread("test_image.jpg")

# 第一次检测
detections1, mask1 = detect_color_shape(
    image, lower_red1, upper_red1, 
    shape_type="circle", min_area=500
)

# 第二次检测(另一红色区间)
detections2, mask2 = detect_color_shape(
    image, lower_red2, upper_red2, 
    shape_type="circle", min_area=500
)

# 合并结果
all_detections = detections1 + detections2
combined_mask = mask1 | mask2

# 在原图上绘制结果
for (x, y, w, h, contour) in all_detections:
    cv2.drawContours(image, [contour], -1, (0, 255, 0), 2)
    cv2.circle(image, (x, y), 3, (0, 0, 255), -1)
    cv2.putText(image, "Red Circle", (x-20, y-h-10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

cv2.imwrite("result.jpg", image)
print(f"检测到 {len(all_detections)} 个红色圆形物体")

1.5 常见颜色 HSV 范围参考

颜色 H 下限 H 上限 S 下限 S 上限 V 下限 V 上限
红色 0/170 10/180 120 255 70 255
橙色 11 25 120 255 70 255
黄色 26 35 120 255 70 255
绿色 36 70 120 255 70 255
青色 71 99 120 255 70 255
蓝色 100 124 120 255 70 255
紫色 125 155 120 255 70 255
粉色 156 169 120 255 70 255
黑色 0 180 0 255 0 46
灰色 0 180 0 43 47 221
白色 0 180 0 30 222 255

注意:不同相机的白平衡和亮度设置不同,实际使用时需要根据你的图像微调范围。建议用 OpenCV 窗口滑动条调参:

def create_hsv_trackbars(window_name="HSV Tuner"):
    cv2.namedWindow(window_name)
    cv2.createTrackbar("H_min", window_name, 0, 180, lambda x: None)
    cv2.createTrackbar("H_max", window_name, 180, 180, lambda x: None)
    cv2.createTrackbar("S_min", window_name, 0, 255, lambda x: None)
    cv2.createTrackbar("S_max", window_name, 255, 255, lambda x: None)
    cv2.createTrackbar("V_min", window_name, 0, 255, lambda x: None)
    cv2.createTrackbar("V_max", window_name, 255, 255, lambda x: None)

def get_hsv_from_trackbars(window_name="HSV Tuner"):
    h_min = cv2.getTrackbarPos("H_min", window_name)
    h_max = cv2.getTrackbarPos("H_max", window_name)
    s_min = cv2.getTrackbarPos("S_min", window_name)
    s_max = cv2.getTrackbarPos("S_max", window_name)
    v_min = cv2.getTrackbarPos("V_min", window_name)
    v_max = cv2.getTrackbarPos("V_max", window_name)
    return np.array([h_min, s_min, v_min]), np.array([h_max, s_max, v_max])

1.6 优缺点分析

优点

  • 计算量极小,100MHz Cortex-M 可以实时处理 QVGA 图像
  • 代码简单,容易理解和调试
  • 参数可调,适合固定场景

缺点

  • 对光照变化敏感,光照一变颜色就不准了
  • 对遮挡敏感,部分遮挡会改变轮廓形状
  • 只能检测大致形状,无法区分语义

方案二:Hough 变换检测圆形/直线

2.1 原理简介

Hough 变换是一种经典的形状检测算法,它通过投票机制在参数空间中找到最可能的形状参数。对于圆形,参数空间是 (x, y, r) 三个维度:圆心坐标 + 半径。

2.2 OpenCV 实现

def detect_circles_hough(
    image, 
    dp=1.2, 
    min_dist=50, 
    param1=50, 
    param2=30, 
    min_radius=10, 
    max_radius=100
):
    """
    使用 Hough 变换检测圆形
    
    参数说明:
        dp: 累加器分辨率反比,1 = 原图分辨率
        min_dist: 两个圆心之间的最小距离(避免重复检测)
        param1: Canny 边缘检测高阈值
        param2: 累加器阈值,越大检测越少但越准
        min_radius/max_radius: 半径范围
    
    返回:
        circles: [(x, y, r), ...]
    """
    # 转灰度
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # 高斯模糊降噪
    gray = cv2.GaussianBlur(gray, (5, 5), 0)
    
    # Hough 变换检测圆
    circles = cv2.HoughCircles(
        gray, 
        cv2.HOUGH_GRADIENT, 
        dp=dp, 
        minDist=min_dist,
        param1=param1, 
        param2=param2, 
        minRadius=min_radius, 
        maxRadius=max_radius
    )
    
    result = []
    if circles is not None:
        circles = np.uint16(np.around(circles))
        for i in circles[0, :]:
            x, y, r = i[0], i[1], i[2]
            result.append((x, y, r))
    
    return result

# 使用示例
image = cv2.imread("curling.jpg")
circles = detect_circles_hough(
    image, 
    min_radius=80, 
    max_radius=120  # 冰壶直径约 100 像素
)

# 绘制结果
for (x, y, r) in circles:
    cv2.circle(image, (x, y), r, (0, 255, 0), 2)  # 圆周
    cv2.circle(image, (x, y), 3, (0, 0, 255), -1)  # 圆心

print(f"检测到 {len(circles)} 个冰壶")

2.3 Hough 圆检测结果示意

冰面

Hough 变换可以准确检测已知半径的圆形冰壶

Hough 圆检测冰壶效果示意图

2.4 优缺点分析

优点

  • 计算量比轮廓检测略大,但仍然很小
  • 专门针对圆形检测,效果比轮廓好
  • 可以直接得到半径和圆心坐标,定位准确

缺点

  • 需要知道大致半径范围,半径范围设置不好就漏检
  • 重叠圆容易误检
  • 对椭圆形变形处理不好
  • 只能检测圆,不能检测任意形状

方案三:Blob 分析(连通域检测)

3.1 原理简介

Blob(斑点/连通域)分析是指先找出图像中所有颜色相似的连通区域,然后根据面积、圆形度、惯性比等特征筛选目标。OpenCV 提供了 SimpleBlobDetector 方便使用。

3.2 OpenCV 实现

def detect_blobs_by_color_and_shape(
    image,
    color_lower,
    color_upper,
    min_area=100,
    max_area=10000,
    min_circularity=0.5,
    max_circularity=1.0,
    min_inertia_ratio=0.5,
    min_convexity=0.5
):
    """
    基于 Blob 分析检测指定颜色和形状的物体
    
    参数:
        min_inertia_ratio: 惯性比,圆 ≈ 1,长条 ≈ 0
        min_convexity: 凸包面积比,越凸越接近 1
    """
    # 颜色分割得到掩码
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, color_lower, color_upper)
    
    # 设置 Blob 检测器参数
    params = cv2.SimpleBlobDetector_Params()
    
    # 按面积过滤
    params.filterByArea = True
    params.minArea = min_area
    params.maxArea = max_area
    
    # 按圆形度过滤
    params.filterByCircularity = True
    params.minCircularity = min_circularity
    params.maxCircularity = max_circularity
    
    # 按惯性比过滤(判断是否圆)
    params.filterByInertia = True
    params.minInertiaRatio = min_inertia_ratio
    
    # 按凸性过滤
    params.filterByConvexity = True
    params.minConvexity = min_convexity
    
    # 创建检测器并检测
    detector = cv2.SimpleBlobDetector_create(params)
    keypoints = detector.detect(mask)
    
    # 提取结果
    result = []
    for kp in keypoints:
        x = int(kp.pt[0])
        y = int(kp.pt[1])
        size = int(kp.size)
        r = int(size / 2)
        result.append((x, y, r))
    
    return result, mask, keypoints

3.3 Blob 特征参数说明

参数 取值范围 完美圆形值 说明
面积 像素数 - 过滤太小太大的目标
圆形度 0~1 ≈ 1.0 越接近 1 越圆
凸性 0~1 ≈ 1.0 凸包面积 / 轮廓面积
惯性比 0~1 ≈ 1.0 对圆形的度量

圆形度 vs 惯性比

  • 圆形度对轮廓噪声更敏感,因为它完全基于周长和面积
  • 惯性比基于二阶矩,对噪声鲁棒性更好

3.4 优缺点分析

优点

  • 内置参数丰富,可以灵活过滤各种形状
  • 一次检测多个 Blob,批量处理高效
  • 参数直观,容易调整
  • 计算量中等,适合嵌入式

缺点

  • 需要先做颜色分割,对光照变化敏感
  • 重叠 Blob 难以分开
  • 对于非凸形状处理不好

方案四:深度学习目标检测(YOLO / SSD)

4.1 方案架构

当背景复杂、光照变化大时,传统方法效果不好,可以使用深度学习目标检测直接训练一个检测器:

输入图像 任意尺寸,RGB YOLO / SSD 前向传播 输出边框 + 类别 + 置信度 NMS 非极大值抑制 去除重叠框,保留最佳检测 输出:所有检测到的目标 (x, y, w, h, class_id, conf) 深度学习
深度学习目标检测流程

4.2 使用 YOLOv8 检测示例

需要先安装 ultralytics:

pip install ultralytics

代码实现:

from ultralytics import YOLO

# 加载预训练模型(或你自己训练的模型)
model = YOLO("yolov8n.pt")  # 检测自定义类需要重新训练

# 假设我们训练了一个检测 "red_circle", "blue_square" 的模型
# model = YOLO("best.pt")  # 你的训练结果

def detect_objects_yolo(image, conf_threshold=0.5):
    """YOLOv8 目标检测"""
    results = model(image, conf=conf_threshold)[0]
    
    detections = []
    for box in results.boxes:
        x1, y1, x2, y2 = box.xyxy[0].tolist()
        conf = box.conf[0].item()
        class_id = int(box.cls[0].item())
        class_name = model.names[class_id]
        
        x = int((x1 + x2) / 2)
        y = int((y1 + y2) / 2)
        w = int(x2 - x1)
        h = int(y2 - y1)
        
        detections.append({
            "bbox": (x, y, w, h),
            "class_name": class_name,
            "confidence": conf
        })
    
    return detections

# 使用示例
image = cv2.imread("test.jpg")
detections = detect_objects_yolo(image, conf_threshold=0.5)

for det in detections:
    x, y, w, h = det["bbox"]
    name = det["class_name"]
    conf = det["confidence"]
    
    x1 = x - w // 2
    y1 = y - h // 2
    x2 = x + w // 2
    y2 = y + h // 2
    
    cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
    cv2.putText(image, f"{name} {conf:.2f}", (x1, y1-10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

print(f"检测到 {len(detections)} 个目标")

4.3 数据收集与训练

如果要检测特定颜色和形状的组合,你需要收集自己的数据集:

  1. 收集数据:拍摄至少 100-200 张包含目标的不同角度、光照下的图片
  2. 标注数据:使用 LabelImg 或 Roboflow 标注边框和类别
    • 类别示例:red_circle, red_square, blue_circle, blue_square
  3. 划分数据集:训练集 80%,验证集 10%,测试集 10%
  4. 训练模型:使用 YOLOv8 训练
    yolo detect train data=data.yaml model=yolov8n.pt epochs=50 imgsz=640
    
  5. 量化转换:如果部署到嵌入式设备,需要转换成 INT8 量化模型

4.4 部署到嵌入式设备

  • Linux + NPU:全精度 YOLOv8n 可以在 1GHz 芯片上达到 30+ FPS
  • MCU + NPU:例如 ESP32-S3、STM32+CMSIS-NN,可以运行 YOLOv5n 量化版
  • 纯 MCU 无 NPU:不建议,速度太慢(几秒一帧)

4.5 优缺点分析

优点

  • 鲁棒性强,对光照变化、遮挡、背景干扰不敏感
  • 可以同时检测多种颜色形状组合,直接输出分类结果
  • 端到端检测,不需要手动设计多个步骤
  • 数据越多,效果越好

缺点

  • 计算量大,需要带 NPU 的 MCU 或 Linux 才能实时运行
  • 需要收集标注数据,训练成本高
  • 参数多,存储空间要求大(几 MB 到几十 MB)
  • 调试困难,错了不好分析原因

性能对比

5.1 资源占用对比(QVGA 320×240 图像)

方案 CPU 占用(Cortex-M7 @ 200MHz) 帧率 RAM 占用 Flash 占用
颜色分割 + 轮廓 ~20% 30-50 FPS ~50KB ~5KB
Hough 圆检测 ~40% 15-30 FPS ~100KB ~8KB
Blob 分析 ~30% 20-40 FPS ~80KB ~7KB
YOLOv8n(NPU) ~10%(NPU 算) 20-30 FPS ~10MB ~3MB

测试条件:单目标,图像大小 320×240,ARM Cortex-M7 平台

5.2 准确率对比(复杂背景)

方案 颜色变化鲁棒性 形状变化鲁棒性 遮挡鲁棒性 背景干扰鲁棒性
颜色分割 + 轮廓 ⭐⭐ ⭐⭐⭐
Hough 圆检测 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐
Blob 分析 ⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐
YOLO 深度学习 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

⭐⭐⭐⭐⭐ = 最好,⭐ = 最差

选型建议

根据你的场景选择最合适的方案:

场景 推荐方案 理由
背景简单,光照稳定,纯 MCU 部署 颜色分割 + 轮廓检测 计算量最小,满足实时性
需要检测多个已知半径的圆形 Hough 变换 专门针对圆形,定位准
需要检测多个颜色形状,批量处理 Blob 分析 参数丰富,一次检测多个
背景复杂,光照多变,有 NPU/Linux YOLO 深度学习 鲁棒性最好,准确率最高

实战案例:冰壶检测

6.1 问题分析

冰壶检测特点:

  • 冰面背景比较均匀
  • 冰壶是标准圆形,直径固定
  • 颜色和冰面对比明显(冰壶深色,冰面浅色)
  • 需要实时检测(30 FPS 以上)

6.2 推荐方案

方案:颜色分割 + Hough 变换

# 冰壶检测完整示例
def detect_curling_stones(image, min_radius=60, max_radius=100):
    # 冰壶是深色,冰面是浅色
    # 转灰度 + 阈值分割
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    _, mask = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
    
    # 形态学处理
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    
    # Hough 检测圆
    circles = cv2.HoughCircles(
        mask, cv2.HOUGH_GRADIENT, 
        dp=1.2, min_dist=80,
        param1=50, param2=25,
        minRadius=min_radius, maxRadius=max_radius
    )
    
    result = []
    if circles is not None:
        circles = np.uint16(np.around(circles))
        for i in circles[0, :]:
            result.append((i[0], i[1], i[2]))
    
    return result, mask

GitHub 上已经有专门做冰壶检测的项目:https://github.com/AIcurling/Stone-Detection,正好是我们讨论的思路,可以去参考一下。

开源参考代码

本文完整代码已上传 GitHub,欢迎 Star/Fork:

总结

  • 传统图像处理方案适合简单背景、纯 MCU 部署,代码量小,速度快,但对环境变化敏感
  • 深度学习方案适合复杂背景、有 NPU/Linux 的平台,准确率和鲁棒性高,但需要数据和更大算力
  • 根据你的硬件平台和场景复杂度选择合适方案,不要盲目追求深度学习
  • 冰壶这种规则形状+均匀背景,用传统方法就足够了,而且可以做到 30 FPS 以上在 Cortex-M7 上

本文基于 OpenCV 官方文档和实际项目经验整理,适合嵌入式计算机视觉入门学习。

参考资料

  1. OpenCV 官方文档:Image Segmentation, Hough Transform, Blob Detection
  2. AIcurling/Stone-Detection - GitHub 冰壶检测项目: https://github.com/AIcurling/Stone-Detection
  3. Ultralytics YOLOv8 文档: https://docs.ultralytics.com/
  4. Learning OpenCV 3: Computer Vision in C++ with the OpenCV Library
  5. Roboflow 自定义数据标注: https://roboflow.com/