引言

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

  • 冰壶比赛自动计分系统:检测冰面上的圆形冰壶
  • 工业零件分拣:检测红色圆形螺丝、蓝色方形螺母
  • 自动驾驶交通标志识别:检测圆形红圈禁令标志
  • 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/17010/18012025570255
橙色112512025570255
黄色263512025570255
绿色367012025570255
青色719912025570255
蓝色10012412025570255
紫色12515512025570255
粉色15616912025570255
黑色01800255046
灰色018004347221
白色0180030222255

注意:不同相机的白平衡和亮度设置不同,实际使用时需要根据你的图像微调范围。建议用 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 方案架构

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

输入图像任意尺寸,RGBYOLO / 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/LinuxYOLO 深度学习鲁棒性最好,准确率最高

实战案例:冰壶检测

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/