引言
在工业检测、机器人视觉、智能分拣等应用场景中,我们经常需要实时检测特定颜色和形状的物体。例如:
- 冰壶比赛自动计分系统:检测冰面上的圆形冰壶
- 工业零件分拣:检测红色圆形螺丝、蓝色方形螺母
- 自动驾驶交通标志识别:检测圆形红圈禁令标志
- AGV 小车导航:识别地面彩色圆形二维码
本文将从简单到复杂,介绍几种常见的实现方案,对比它们的性能,并提供完整的开源参考代码,帮助你根据实际场景选择最合适的方案。
方案对比总览
我们主要对比四种主流方案:
| 方案 | 原理 | 计算量 | 准确率 | 适合场景 | MCU 能否运行 |
|---|---|---|---|---|---|
| 颜色分割 + 轮廓检测 | 阈值分割 + 形状分析 | 极低 | 对颜色形状变化敏感 | 背景简单、光照稳定 | ✅ Cortex-M7 可以 |
| 颜色空间转换 + Hough 变换 | Hough 圆/直线检测 | 低 | 圆形检测较好 | 固定形状检测 | ✅ Cortex-M4 可以 |
| Blob 分析 + 特征匹配 | 连通域分析 + 形状分类 | 中 | 中等 | 多目标批量处理 | ✅ Cortex-M7 可以 |
| 深度学习目标检测 | YOLO/SSD 直接检测 | 高 | 鲁棒性强 | 复杂背景、光照变化 | ❌ 需要 MCU+NPU 或 Linux |
下面详细介绍每种方案的实现。
方案一:颜色分割 + 轮廓检测
1.1 算法流程
1.2 核心原理
- 颜色空间转换:从 RGB 转到 HSV 颜色空间,更容易按颜色分割
- 阈值分割:对 H/S/V 三个通道设置范围,得到二值掩码
- 形态学处理:腐蚀 + 膨胀去除噪声
- 轮廓查找:找到所有连通区域
- 形状特征计算:计算面积、周长、圆形度、矩形度等特征
- 特征匹配:筛选符合指定形状的目标
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 圆检测结果示意
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 方案架构
当背景复杂、光照变化大时,传统方法效果不好,可以使用深度学习目标检测直接训练一个检测器:
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 数据收集与训练
如果要检测特定颜色和形状的组合,你需要收集自己的数据集:
- 收集数据:拍摄至少 100-200 张包含目标的不同角度、光照下的图片
- 标注数据:使用 LabelImg 或 Roboflow 标注边框和类别
- 类别示例:
red_circle,red_square,blue_circle,blue_square
- 类别示例:
- 划分数据集:训练集 80%,验证集 10%,测试集 10%
- 训练模型:使用 YOLOv8 训练
yolo detect train data=data.yaml model=yolov8n.pt epochs=50 imgsz=640 - 量化转换:如果部署到嵌入式设备,需要转换成 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:
- https://github.com/wh2000hh/color-shape-detection
- 包含:
- 四种方案的完整 Python 实现
- HSV 调参工具
- 评估脚本
- 冰壶检测示例
总结
- 传统图像处理方案适合简单背景、纯 MCU 部署,代码量小,速度快,但对环境变化敏感
- 深度学习方案适合复杂背景、有 NPU/Linux 的平台,准确率和鲁棒性高,但需要数据和更大算力
- 根据你的硬件平台和场景复杂度选择合适方案,不要盲目追求深度学习
- 冰壶这种规则形状+均匀背景,用传统方法就足够了,而且可以做到 30 FPS 以上在 Cortex-M7 上
本文基于 OpenCV 官方文档和实际项目经验整理,适合嵌入式计算机视觉入门学习。
参考资料
- OpenCV 官方文档:Image Segmentation, Hough Transform, Blob Detection
- AIcurling/Stone-Detection - GitHub 冰壶检测项目: https://github.com/AIcurling/Stone-Detection
- Ultralytics YOLOv8 文档: https://docs.ultralytics.com/
- Learning OpenCV 3: Computer Vision in C++ with the OpenCV Library
- Roboflow 自定义数据标注: https://roboflow.com/