OpenCV-python小玩意12 颜色定位方案
0.写在前面
这阵子沉迷在AI硬件复刻领域,Esp32芯片+小智方案可以玩出非常多的花样。后面想逐步写写这方面的内容。当然了,多模态的AI硬件,是无法绕过视觉的。
今天来研究研究如何在图像中定位红色的方块。
有人就会问了,系列中上一篇文章是10,这篇就12了,请问11哪里去了?好吧,我承认,11让我丢了,原计划是写透视变换来着,最近比较忙,后面给补上吧。
1.OpenCV中的颜色系统
因为找的是红色的方块,那么首先使用颜色去做定位是个不错的选择。
下面让我们熟悉下OpenCV中的颜色系统。
1.1 BGR颜色空间
这是OpenCV默认的颜色表示方式,三个通道分别代表蓝(B)、绿(G)、红®,每个通道值范围通常为 0-255(8位图像)。
当我们加载图像时,默认的就是用BGR空间。
import cv2
image = cv2.imread("image.jpg") # 默认加载为BGR格式
1.2 RGB颜色空间
这是一个更常用的方式,比如网页啦,其他库啦。三个通道分别代表红®、绿(G)、蓝(B),与BGR只是通道顺序不同。
与BGR的转换也很简单,用到了之前提到的cvtColor方法。
rgb_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB)
1.3 HSV 颜色空间
在《小玩意4 预处理》中简单提到过,它也是三个通道:色调(H)、饱和度(S)、明度(V)
H: 0-180(OpenCV中),表示颜色类型
S: 0-255,表示颜色纯度
V: 0-255,表示明度
它对光照变化较不敏感,所以它可以用到我们今天的颜色检测和分割场景,以及光照变化较大的环境。
hsv图像的获取方式如下:
hsv_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV)
在HSV空间中定义颜色范围时,可以参考以下经验值:
# 红色(需要两个范围)
lower_red1 = np.array([0, 70, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 70, 50])
upper_red2 = np.array([180, 255, 255])
# 绿色
lower_green = np.array([40, 70, 50])
upper_green = np.array([80, 255, 255])
# 蓝色
lower_blue = np.array([100, 70, 50])
upper_blue = np.array([130, 255, 255])
1.4 HSL/HSV 颜色空间
有点类似HSV,但L表示亮度(Lightness):
H: 0-180
S: 0-255
L: 0-255
好吧,是不是已经蒙圈了,这亮度和明度是什么鬼?还好找到了两张对比图。

Hue(色调、色相):相当于基调,是图中俯视图得到的圆,圆上不同位置的颜色基调不同,把颜色分成了360°,每个位置有不同的颜色基调;
Saturation(饱和度、色彩纯净度):纯度,沿着俯视图得到的圆的半径看,因为圆弧上的点代表该处的颜色的基调,那么半径上就是从纯白色到该基调颜色过渡过程中不同位置的纯度,在圆心处纯度为0,在圆弧上(该色调)纯度为100;
1.5 灰度图像
灰度图像也算颜色系统中的一员,它是单通道图像,值范围 0-255,特点是存储空间小,处理速度快。图像预处理时我们经常用到。
1.6 其他的
还有LAB颜色空间和YCrCb颜色空间,请大家自行脑补。
2. 掩膜
红色区域的提取,准备用红色掩膜来完成。
查了下百科中的定义,图像掩模(英文名:mask)是通过选定的图形或物体遮挡待处理图像,以控制处理区域或过程的技术手段。其外文名称为mask,核心功能为限定图像处理的操作范围。
在OpenCV中我们这样处理:
mask1 = cv2.inRange(hsv, lower_red1, upper_red1) # 低H值红色区域
mask2 = cv2.inRange(hsv, lower_red2, upper_red2) # 高H值红色区域
red_mask = cv2.bitwise_or(mask1, mask2) # 按位或运算,合并两个红色区域
2.1 cv2.inRange函数
cv2.inRange用于基于颜色/亮度范围进行像素筛选,生成二进制掩膜。
函数原型:
mask = cv2.inRange(src, lowerb, upperb)
参数解析:
| 参数 | 类型 | 说明 |
|---|---|---|
src |
numpy.ndarray | 输入图像(通常为HSV或BGR格式) |
lowerb |
标量或数组 | 范围下限(单通道标量,多通道数组) |
upperb |
标量或数组 | 范围上限(与lowerb同类型) |
举个栗子:
# 检测蓝色物体(BGR空间)
lower_blue = np.array([100, 0, 0])
upper_blue = np.array([255, 50, 50])
blue_mask = cv2.inRange(bgr_image, lower_blue, upper_blue)
2.2 cv2.bitwise_or函数
cv2.bitwise_or执行按位或运算,常用于掩膜合并。
函数原型:
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])
参数解析:
| 参数 | 类型 | 说明 |
|---|---|---|
src1 |
numpy.ndarray | 第一个输入数组 |
src2 |
numpy.ndarray | 第二个输入数组 |
dst |
numpy.ndarray | 输出数组(可选) |
mask |
numpy.ndarray | 操作掩膜(可选) |
举个栗子:goto到上面。
3.形态学操作
我们先用getStructuringElement构造一个基础核,然后用开闭运算做特征提取。
3.1 getStructuringElement
用于创建结构元素(Structuring Element),这是所有形态学操作的基础核。
cv2.getStructuringElement(shape, ksize[, anchor])
参数解析
| 参数 | 类型 | 说明 |
|---|---|---|
shape |
int | 结构元素形状,可选: • cv2.MORPH_RECT - 矩形• cv2.MORPH_ELLIPSE - 椭圆• cv2.MORPH_CROSS - 十字形 |
ksize |
tuple | 结构元素大小,(width, height) |
anchor |
tuple | 锚点位置,默认(-1,-1)表示中心 |
举个栗子:
# 创建5x5矩形结构元素
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
# 创建7x7椭圆结构元素
ellipse_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
3.2 cv2.morphologyEx
这是高级形态学操作,它执行指定的形态学操作,是腐蚀、膨胀等操作的组合封装。
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
参数解析
| 参数 | 说明 |
|---|---|
src |
输入图像(通常为二值图像) |
op |
操作类型: • cv2.MORPH_OPEN - 开运算• cv2.MORPH_CLOSE - 闭运算• cv2.MORPH_GRADIENT - 形态学梯度• cv2.MORPH_TOPHAT - 顶帽运算• cv2.MORPH_BLACKHAT - 黑帽运算 |
kernel |
结构元素(由getStructuringElement创建) |
iterations |
操作执行次数(默认为1) |
3.2.1 开运算 (MORPH_OPEN)
公式:开运算 = 腐蚀 → 膨胀
效果:消除小物体、平滑轮廓、断开狭窄连接
适用场景:去除噪声(白噪点)
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
3.2.2 闭运算 (MORPH_CLOSE)
公式:闭运算 = 膨胀 → 腐蚀
效果:填充小孔洞、连接邻近物体、平滑边界
适用场景:填补缺口(黑孔洞)
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
3.2.3 形态学梯度 (MORPH_GRADIENT)
公式:梯度 = 膨胀 - 腐蚀
效果:获取物体边缘轮廓
适用场景:边缘检测
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
4. 轮廓处理
这里我们已经很熟悉了,在系列的前面文章中,多次用到了:findContours、contourArea和drawContours等函数。
5. 计算中心点
这次用到了cv2.moments函数,它是OpenCV中用于计算图像矩(Image Moments)的核心函数,在轮廓分析中起着重要作用。
基本定义
moments = cv2.moments(contour_or_array)
输入参数
| 参数 | 类型 | 说明 |
|---|---|---|
contour_or_array |
轮廓点集或二值图像 | 可以是: • 轮廓点集(N×1×2数组) • 二值图像(单通道,非零像素被视为前景) |
输出矩的类型:
函数返回一个包含24个不同矩的字典对象:
空间矩(Spatial Moments)
m00,m10,m01,m20,m11,m02,m30,m21,m12,m03
中心矩(Central Moments)
mu20,mu11,mu02,mu30,mu21,mu12,mu03
归一化中心矩(Hu矩的基础)
nu20,nu11,nu02,nu30,nu21,nu12,nu03
举个栗子:
import cv2
import numpy as np
# 假设已获得轮廓
contour = largest_square# 来自之前的检测结果
# 计算矩
M = cv2.moments(contour)
# 计算中心
if M["m00"] != 0:
center = (int(M["m10"]/M["m00"]), int(M["m01"]/M["m00"]))
print(f"中心坐标: {center}")
# 计算轮廓方向
if M["mu20"] != M["mu02"]:
theta = 0.5 * np.arctan2(2*M["mu11"], M["mu20"]-M["mu02"])
print(f"旋转角度: {np.degrees(theta):.2f}°")
# 计算偏心率
lambda1 = (M["mu20"] + M["mu02"]) / 2 + np.sqrt(4*M["mu11"]**2 + (M["mu20"]-M["mu02"])**2) / 2
lambda2 = (M["mu20"] + M["mu02"]) / 2 - np.sqrt(4*M["mu11"]**2 + (M["mu20"]-M["mu02"])**2) / 2
eccentricity = np.sqrt(1 - lambda2/lambda1)
print(f"偏心率: {eccentricity:.2f}")
else:
print("零阶矩为0,无法计算")
4. 最后
上述梳理了定位红色方块的过程及用到的核心知识,那么show you the code:
import cv2
import numpy as np
def detect_red_square(image_path):
# 1. 加载图像
image = cv2.imread(image_path)
if image is None:
print("无法加载图像")
return None
# 2. 转换为HSV颜色空间
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 3. 定义红色的HSV范围
# 红色在HSV中有两个范围(色轮的两端)
lower_red1 = np.array([0, 70, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 70, 50])
upper_red2 = np.array([180, 255, 255])
# 4. 创建红色掩膜
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
# 5. 形态学操作(去除噪声)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel)
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel)
# 6. 寻找轮廓
contours, _ = cv2.findContours(red_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 7. 筛选轮廓
red_squares = []
for cnt in contours:
# 计算轮廓面积
area = cv2.contourArea(cnt)
# 计算最小外接矩形
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int32(box)
# 计算矩形度(轮廓面积/外接矩形面积)
rect_area = cv2.contourArea(box)
rect_degree = area / rect_area if rect_area > 0 else 0
# 检查是否是近似矩形
if rect_degree > 0.8:
red_squares.append(box)
# 8. 绘制结果
if len(red_squares) > 0:
# 找出面积最大的红色方块
largest_square = max(red_squares, key=lambda x: cv2.contourArea(x))
# 绘制边界
cv2.drawContours(image, [largest_square], 0, (0, 255, 0), 2)
# 计算中心点
M = cv2.moments(largest_square)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.circle(image, (cX, cY), 5, (0, 0, 255), -1)
print(f"红色方块中心坐标: ({cX}, {cY})")
# 显示结果
# display_img = cv2.resize(image, (512, 512))
display_img = cv2.resize(image, (512, 512), interpolation=cv2.INTER_AREA)
cv2.imshow("Red Square Detection", display_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
return (cX, cY)
print("未检测到红色方块")
return None
# 使用示例
if __name__ == "__main__":
detect_red_square("red3.png")
效果图如下:
#opencv #掩膜 #HSV颜色空间 #图像定位 #cv2.inRange #cv2.bitwise_or #形态学操作 #getStructuringElement
更多推荐




所有评论(0)