opencv — 颜色和轮廓 的 提取&筛选

注:本文使用的编程语言是python。
如果读者使用的是C++,有些代码可能需要自行变更!

前言

  初学opencv的阶段,难免是从枯燥地啃文档和记函数开始。逐一而草率地“过”一遍函数用法,对于初学者而言,其实很难起到学习的进步。只有在具体的实例中,才能更好地理解函数用法和搭配 的 妙用。
  笔者在视觉库cvzone和halcon的启发下,总结了些opencv实现的颜色和轮廓的提取&筛选方法,能够方便地应用在不同的项目之上。
  如果读友是小白,在这里也推荐个B站上的油管搬运教程:opencv超实用实战项目,因为是手把手敲代码的,对初学者非常友好。还有,视频原作者是 巴基斯坦 的 CV工程师 Murtaza。这边附上他油管的主页 Murtaza’s Workshop,感兴趣的朋友可以看看。
  言归正传,我们开始吧!

Part1. 颜色提取&筛选

  颜色提取&筛选 是最直观的图像处理方式,简单粗暴但不失为有效。
  其主要步骤如下:

  • 1.将原图像由 RGB模型转为 HSV模型
    (因为HSV模型有专门的 H色调 通道,更方便颜色的提取s)
  • 2.确定 目标提取颜色 HSV范围 (可以不止一个)
  • 3.使用inRange()函数,获取图像掩膜
  • 4.使用图像位操作,将掩膜进行合并
  • 5.用掩膜覆盖原图像,使其仅保留预期的部分

针对步骤2,这里给出常用的HSV范围:

颜色HSV 范围
起始范围结束范围
全部(0, 0, 0) (180, 255, 255)
黑色(0, 0, 0) (180, 255, 46)
灰色(0, 0, 46) (180, 43, 220)
白色(0, 0, 211) (180, 30, 255)
红色(0, 43, 46) (10, 255, 255)
(156, 43, 46) (180, 255, 255)
橙色(11, 43, 46) (25, 255, 255)
黄色(26, 43, 46) (34, 255, 255)
绿色(35, 43, 46) (77, 255, 255)
青色(78, 43, 46) (99, 255, 255)
蓝色(100, 43, 46) (124, 255, 255)
紫色(125, 43, 46) (155, 255, 255)
  从表中不难看出,区分各颜色的主要参数,就在于H通道的取值。表内颜色的取值边界比较宽泛模糊,如果有更精准的提取需求,建议还是手动调参。

  接下来,就是代码部分。为了使模块更通用,笔者是把功能写成python类进行封装。调用类实例化的对象,以实现颜色提取的功能。主要是在魔术方法__call__()内实现。

import cv2
import numpy as np

# 基本的导入模块

# 设置掩膜函数
def setMask(src: np.ndarray, mask: np.ndarray) -> np.ndarray:
    channels = cv2.split(src)
	# 通道分离
    result = []
    for i in range(len(channels)):
        result.append(cv2.bitwise_and(channels[i], mask))
		# 各通道于掩膜进行图像和操作
    dest = cv2.merge(result)
	# 通道合并
    return dest

class ColorFilter(object):
    def __init__(self):
        self.colorRange = []
		# 这里是颜色筛选的范围 
		# 存储格式为 [ ((H起始,S起始,V起始),(H结束,S结束,V结束)), ... ]

    def __call__(self, src: np.ndarray) -> np.ndarray:
		# 必要的函数注释
        finalMask = np.zeros_like(src)[:, :, 0]
		# finalMask指的是 最终合成的掩膜
        hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
		# 将图像转为HSV通道
        for each in self.colorRange:
			# 逐一获取HSV范围
	        lower, upper = each
			# HSV范围解构为 起始 和 结束
            mask = cv2.inRange(hsv, lower, upper)
			# 制作该HSV范围的掩膜
            finalMask = cv2.bitwise_or(finalMask, mask)
			# 掩膜合并 目标颜色 = 颜色1 + 颜色2 + ... + 颜色n
			# 注:inRange()不在HSV范围内的部分 数值为 0
			
        dest = setMask(src, finalMask)
		# 设置掩膜
        return dest
	
# 调用测试
if __name__ == '__main__':
	src = cv2.imread(r'xxx.png')	# 图片路径
	
	colorFilter = ColorFilter()		# 初始化ColorFilter()对象
	colorFilter.colorRange = [
		((xxx,xxx,xxx),(xxx,xxx,xxx)),	# Color A
		((xxx,xxx,xxx),(xxx,xxx,xxx))	# Color B
	]
	dest = colorFilter(src)
	cv2.imshow('dest', dest)
	cv2.waitKey(0)

  笔者在这里以opencv的logo为例,对红、蓝色的区域进行提取。如下是效果图:
颜色提取效果图
  对于其他复杂的情形,可以修改上述代码中,对掩膜的图像位操作,以实现复杂的区域的合并、剪切、取反等效果。

Part2. 轮廓提取&筛选

  相比较颜色提取,轮廓提取对于实际应用的普适性更好,也相应得更复杂。
  其主要步骤如下:

  • 1.彩色图转灰度图,二值化处理。
    (如果图像噪声多,可适当使用滤波器)
  • 2.使用findContours()函数,提取轮廓
  • 3.使用arcLength(),contourArea()函数获取 轮廓周长和面积
    (也可以引入其他特征量)
  • 4.筛选轮廓,过滤掉无效轮廓
  • 5.进行 绘制轮廓/制作掩膜 等操作

针对步骤3,这里给出halcon里常用的区域特征量:

特征名称解释取值范围计算公式
面积(Area)轮廓的面积(0,+∞)A
周长(contlength)轮廓线总长(0,+∞)P
紧密度(Compactness)相同周长的圆和当前轮廓的面积比[1,+∞)4*Pi*A/P^2
圆度(Circularity)当前轮廓和最小外接圆的面积比(0,1]A/(Pi*R外接^2)
凸度(Convexity)当前轮廓和凸包的面积比(0,1]A/A凸
矩形度(Rectangularity)当前轮廓和最小外接矩形的比值(0,1]A/A矩
以上是常用且在opencv中容易实现的特征量。相关的opencv函数见下:
  • 轮廓周长:arcLength()
  • 轮廓面积:contourArea()
  • 凸包检测:convexHull()
  • 最小外接圆:minEnclosingCircle()
  • 最小外接矩形:minAreaRect()

  接下来,就是代码部分。同上,是封装在python类中实现。因为轮廓的筛选方式很多,这里实现的是简单的依据 轮廓面积和周长范围进行筛选。

import cv2
import numpy as np

# 导入相应的模块

class ContourFilter(object):
    def __init__(self):
        super(ContourFilter, self).__init__()
        # 输入参数区
        self.threshold = lambda image: cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                                             cv2.THRESH_BINARY_INV, 15, 21)
		# 二值化算法,这里使用的是 自适应二值化,也可以使用Canny边缘检测算法 等。
        self.areaRanges = []
		# 轮廓面积范围
		# 存储格式为 [(minArea1,maxArea1),(minArea2,maxArea2),...]
        self.perimeterRanges = []
		# 轮廓周长范围
		# 存储格式同上
        self.contourColor = (255, 127, 127)
		# 轮廓的绘制颜色
        self.contourThickness = 3
		# 轮廓的绘制粗细
        self.inPlace = False
		# 是否处理后显示在原图上
        self.paint = True
		# 是否进行绘制

    def __call__(self, src: np.ndarray) -> np.ndarray:
    	# 必要的函数注释
        if not self.areaRanges:
            self.areaRanges = [(0, float('inf'))]
        if not self.perimeterRanges:
            self.perimeterRanges = [(0, float('inf'))]
		# 如果周长和面积范围未赋值(None),那么默认为(0,+∞)

        if self.inPlace:
            dest = src.copy()
        else:
            dest = np.zeros_like(src)
		# 处理显示在 原图拷贝上 或者 在空图像上

        if len(src.shape) == 3 and src.shape[2] == 3:
            gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
        elif len(src.shape) == 2:
            gray = src
		# 转灰度图处理,如果本身就是单通道,那么不进行转换
		
        binary = self.threshold(gray)
		# 二值化处理
		
        contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
		# 轮廓提取,这里提取的是外轮廓且忽略轮廓层次信息
        resultContours = []
		# 轮廓的筛选结果列表
        for contour in contours:
			# 对每一轮廓进行遍历
            perimeter = cv2.arcLength(contour, True)
			# 计算轮廓长度
            area = cv2.contourArea(contour)
			# 计算轮廓面积
			
            for perimeterRange, areaRange in zip(self.perimeterRanges, self.areaRanges):
                if perimeterRange[0] < perimeter <= perimeterRange[1] and areaRange[0] < area <= areaRange[1]:
                    resultContours.append(contour)
			# 记录符合筛选条件的轮廓
        if self.paint:
            cv2.drawContours(dest, resultContours, -1, self.contourColor, self.contourThickness)
		# 绘制轮廓
		
        return dest
# 调用测试
if __name__ == '__main__':
	src = cv2.imread(r'xxx.png')	# 图片路径
    contourFilter = ContourFilter()	# 初始化ContourFilter对象
    contourFilter.areaRanges.append((minArea, maxArea))
    contourFilter.perimeterRanges.append((minPerimeter, maxPerimeter))
	# 对轮廓的面积和周长进行条件限制
	dest = colorFilter(src)
	cv2.imshow('dest', dest)
	cv2.waitKey(0)

笔者在这里同样以opencv的logo为例,给出运行的效果图:
轮廓提取效果图
  上述代码用例实现了轮廓的绘制,读者也可以自行更改代码,使其变体为生成掩膜、区域填充等功能。笔者在此就不加赘述。

结束语

  笔者作为初涉计算机视觉领域的在校学生,技术水平有限。本文中倘若出现错误或者值得补充的地方,希望各位读者在评论中指出。衷心感谢每位看到本文的读者!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2022年5月17日
下一篇 2022年5月18日

相关推荐