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
好吧,是不是已经蒙圈了,这亮度和明度是什么鬼?还好找到了两张对比图。
HSV
HSL

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐