基于OpenCV的图像红色区域检测与马赛克处理

7 次阅读

图像中的特定颜色区域检测是计算机视觉中的常见任务,例如识别人脸上的特定标记、检测交通标志、提取产品颜色区域以及对敏感内容进行局部遮挡等。OpenCV提供了丰富的图像处理接口,通过颜色空间转换、阈值分割和形态学操作,可以较为准确地提取图像中的红色区域,并进一步对这些区域执行马赛克处理。

本文以Python和OpenCV为基础,介绍红色区域检测的实现思路,并给出完整的马赛克处理方案。

一、实现原理

普通彩色图片通常采用BGR格式存储,每个像素由蓝色、绿色和红色三个通道组成。直接根据BGR通道数值判断红色区域虽然简单,但实际效果容易受到光照、阴影以及颜色变化的影响。

更适合进行颜色检测的方法是转换到HSV颜色空间。

HSV由色相(Hue)、饱和度(Saturation)和明度(Value)组成:

  • Hue:表示颜色类型;

  • Saturation:表示颜色的纯度;

  • Value:表示颜色的明暗程度。

红色在HSV空间中比较特殊,因为红色位于色相环的两端,因此通常需要设置两个范围进行检测,而不是使用一个连续区间。

例如可以设置:

lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([170, 100, 100])
upper_red2 = np.array([180, 255, 255])

随后分别生成两个红色区域掩膜,再使用按位或操作将它们合并。

二、安装OpenCV环境

Python项目可以直接使用pip安装OpenCV:

pip install opencv-python numpy

安装完成后,可以通过下面的代码验证环境:

import cv2
import numpy as np

print(cv2.__version__)

如果能够正常输出OpenCV版本号,说明基本环境已经配置完成。

三、读取图片并转换HSV颜色空间

假设需要处理的图片名称为input.jpg:

import cv2
import numpy as np

image = cv2.imread("input.jpg")

if image is None:
    raise FileNotFoundError("无法读取图片,请检查图片路径")

hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

这里使用cv2.imread()读取原始图片,然后通过cv2.cvtColor()将BGR图像转换成HSV格式。

需要注意的是,OpenCV中的默认颜色顺序是BGR,而不是常见的RGB。如果后续使用其他图像库显示或处理图片,需要特别留意颜色通道顺序。

四、检测图像中的红色区域

红色的HSV范围通常需要拆成两个区间:

lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([170, 100, 100])
upper_red2 = np.array([180, 255, 255])

通过cv2.inRange()生成掩膜:

mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)

mask = cv2.bitwise_or(mask1, mask2)

最终得到的mask是一张二值图。

红色区域通常为白色,其他区域为黑色。这张掩膜就是后续定位和处理红色区域的基础。

五、使用形态学操作优化检测结果

实际图片中的红色区域可能受到噪声影响,导致掩膜中出现一些零散的小区域。

可以使用形态学操作进行优化。

例如先进行开运算去除小噪点,再通过闭运算填充局部空洞:

kernel = np.ones((5, 5), np.uint8)

mask = cv2.morphologyEx(
    mask,
    cv2.MORPH_OPEN,
    kernel
)

mask = cv2.morphologyEx(
    mask,
    cv2.MORPH_CLOSE,
    kernel
)

开运算主要用于去除细小噪声,而闭运算可以连接邻近区域并填补部分内部缺口。

核的大小会直接影响处理效果。如果设置过小,噪声可能无法有效消除;如果设置过大,则可能导致红色区域边缘被过度处理。

六、获取红色区域的位置

如果目标不仅是得到红色掩膜,还需要对检测到的区域进行进一步处理,可以通过轮廓查找获取红色区域的位置。

contours, _ = cv2.findContours(
    mask,
    cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE
)

遍历轮廓后,可以通过面积过滤掉过小区域:

for contour in contours:
    area = cv2.contourArea(contour)

    if area < 100:
        continue

    x, y, w, h = cv2.boundingRect(contour)

    print("检测区域:", x, y, w, h)

其中:

  • x、y表示矩形左上角坐标;

  • w表示区域宽度;

  • h表示区域高度;

  • area表示轮廓面积。

面积阈值可以根据实际图片分辨率进行调整。

七、对红色区域进行马赛克处理

得到目标区域后,可以对红色区域进行马赛克处理。

马赛克的基本思路是将目标区域缩小,再放大回原尺寸。由于缩小过程会丢失大量细节,重新放大后就会产生明显的块状效果。

例如:

def apply_mosaic(image, x, y, w, h, scale=0.05):
    roi = image[y:y + h, x:x + w]

    if roi.size == 0:
        return

    small_w = max(1, int(w * scale))
    small_h = max(1, int(h * scale))

    small = cv2.resize(
        roi,
        (small_w, small_h),
        interpolation=cv2.INTER_LINEAR
    )

    mosaic = cv2.resize(
        small,
        (w, h),
        interpolation=cv2.INTER_NEAREST
    )

    image[y:y + h, x:x + w] = mosaic

这里的scale用于控制马赛克强度。

例如:

scale=0.1

表示将原区域缩小到约10%的尺寸,再恢复到原来的大小。

如果改成:

scale=0.02

马赛克块会更加明显。

八、完整实现代码

将红色区域检测和马赛克处理结合起来,可以得到一个完整示例:

import cv2
import numpy as np


def apply_mosaic(image, x, y, w, h, scale=0.05):
    roi = image[y:y + h, x:x + w]

    if roi.size == 0:
        return

    small_w = max(1, int(w * scale))
    small_h = max(1, int(h * scale))

    small = cv2.resize(
        roi,
        (small_w, small_h),
        interpolation=cv2.INTER_LINEAR
    )

    mosaic = cv2.resize(
        small,
        (w, h),
        interpolation=cv2.INTER_NEAREST
    )

    image[y:y + h, x:x + w] = mosaic


image = cv2.imread("input.jpg")

if image is None:
    raise FileNotFoundError("无法读取图片")

# 转换为HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

# 红色范围
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([170, 100, 100])
upper_red2 = np.array([180, 255, 255])

# 创建掩膜
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)

mask = cv2.bitwise_or(mask1, mask2)

# 形态学处理
kernel = np.ones((5, 5), np.uint8)

mask = cv2.morphologyEx(
    mask,
    cv2.MORPH_OPEN,
    kernel
)

mask = cv2.morphologyEx(
    mask,
    cv2.MORPH_CLOSE,
    kernel
)

# 查找红色区域
contours, _ = cv2.findContours(
    mask,
    cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE
)

# 对红色区域进行马赛克
for contour in contours:
    area = cv2.contourArea(contour)

    if area < 100:
        continue

    x, y, w, h = cv2.boundingRect(contour)

    apply_mosaic(
        image,
        x,
        y,
        w,
        h,
        scale=0.05
    )

# 保存结果
cv2.imwrite("output.jpg", image)

print("处理完成,结果已保存到 output.jpg")

执行程序后,图片中的红色区域会被检测出来,并针对对应的外接矩形区域进行马赛克处理。

九、只对红色像素进行马赛克

使用外接矩形虽然实现简单,但存在一个问题:矩形内部可能包含大量非红色内容,这些区域也会被一起打码。

如果希望只处理红色区域,可以利用掩膜进行局部替换。

一种常见方式是先对整张图片生成马赛克版本,再利用掩膜把红色区域替换成马赛克结果:

small = cv2.resize(
    image,
    None,
    fx=0.05,
    fy=0.05,
    interpolation=cv2.INTER_LINEAR
)

mosaic_image = cv2.resize(
    small,
    (image.shape[1], image.shape[0]),
    interpolation=cv2.INTER_NEAREST
)

result = image.copy()

result[mask > 0] = mosaic_image[mask > 0]

这种方法不会简单地按照矩形区域进行处理,而是严格按照红色掩膜确定需要修改的像素。

不过,如果红色区域面积很小,整图缩放产生的马赛克颗粒可能与局部区域的尺寸不匹配。因此实际项目中通常需要根据处理目标选择方案。

十、如何提高红色检测准确率

固定HSV阈值并不能适用于所有图片。不同光照条件、相机参数和目标材质都会影响红色区域的实际HSV值。

例如,在光线较暗的图片中,红色区域的V值可能明显降低。如果仍然使用较高的V下限,就可能漏掉目标区域。

可以根据实际图片调整:

lower_red1 = np.array([0, 80, 50])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([170, 80, 50])
upper_red2 = np.array([180, 255, 255])

如果背景中存在大量接近红色的物体,则可以适当提高S值限制,减少低饱和度区域产生的误检。

实际项目中,建议先输出mask观察检测结果,再调整HSV参数,而不是直接固定一套阈值。

十一、避免马赛克区域过大的方法

轮廓的外接矩形可能包含一些不属于红色目标的背景。

可以进一步使用轮廓面积、宽高比例以及区域大小进行筛选:

for contour in contours:
    area = cv2.contourArea(contour)

    if area < 200:
        continue

    x, y, w, h = cv2.boundingRect(contour)

    if w < 10 or h < 10:
        continue

    ratio = w / h

    if ratio > 10 or ratio < 0.1:
        continue

    apply_mosaic(image, x, y, w, h)

这种过滤方式对于处理复杂背景尤其有用,可以减少零散红色噪声导致的误处理。

十二、矩形马赛克与掩膜马赛克的选择

两种方案各有适用场景。

矩形区域马赛克实现简单,计算量较小,适合目标本身接近矩形的场景。例如红色标识、按钮或特定区域。

掩膜马赛克更加精确,适合目标轮廓不规则的情况。它可以最大限度地避免影响红色区域之外的像素。

如果项目对处理边界要求较高,还可以进一步对掩膜进行膨胀:

kernel = np.ones((3, 3), np.uint8)

mask = cv2.dilate(
    mask,
    kernel,
    iterations=1
)

这样能够让马赛克范围略微覆盖目标边缘,避免由于颜色检测误差产生明显的边界残留。

十三、常见问题分析

1. 红色区域检测不到

首先检查图片是否成功读取,然后确认HSV阈值是否适合当前图片。

可以保存掩膜进行观察:

cv2.imwrite("mask.jpg", mask)

如果目标区域没有呈现为白色,需要重新调整HSV范围。

2. 检测到了大量非红色区域

通常是饱和度阈值过低或者背景中存在接近红色的颜色。

可以提高S的最低值:

lower_red1 = np.array([0, 130, 80])
lower_red2 = np.array([170, 130, 80])

同时配合面积过滤和形态学操作减少噪声。

3. 红色区域只检测到一部分

这种情况通常与红色范围设置有关。由于HSV中的红色跨越色相环的0°位置,因此不能只设置:

[0, 10]

而应该同时考虑:

[0, 10]

和:

[170, 180]

两个范围。

4. 马赛克效果不明显

可以进一步降低缩放比例:

scale=0.02

缩小后的图像信息越少,重新放大后形成的马赛克块通常越明显。

5. 图片保存后颜色异常

需要检查是否在OpenCV和其他图像库之间混用了RGB与BGR格式。

OpenCV默认使用BGR顺序,因此如果图片数据来自PIL、Matplotlib等工具,通常需要进行颜色通道转换。

十四、实际应用场景

基于OpenCV的颜色检测和马赛克处理可以应用在许多图像处理场景中,例如:

  • 自动检测指定颜色的目标;

  • 对特定颜色区域进行隐私遮挡;

  • 商品图片局部信息处理;

  • 工业视觉中的颜色缺陷检测;

  • 交通标志识别中的颜色筛选;

  • 视频监控中的目标区域处理;

  • 图像预处理和视觉数据清洗。

如果处理的是视频,只需要逐帧读取图像,然后重复执行HSV转换、红色区域检测和马赛克处理即可。

核心流程可以概括为:

读取图像
   ↓
BGR转换HSV
   ↓
设置红色HSV范围
   ↓
生成红色掩膜
   ↓
形态学去噪
   ↓
查找目标区域
   ↓
筛选有效轮廓
   ↓
执行马赛克
   ↓