基于 OpenCV 的颜色特征识别
一、实验目的
通过对图像捕捉和颜色特征提取,了解机器视觉的一般工作流程,掌握 OpenCV 的使用及基本图像处理算法。
二、实验内容
1. 图像传感器驱动应用
2. 图像直方图生成
3. 颜色特征设定及目标识别
4. 基于颜色特征的应用扩展
三、实验设备
个人计算机,Linux OS, USB摄像头,OpenCV开发库
四、实验代码分析
1.本次实验主要是通过调用摄像头,并且读取视频中的每一帧图像,将图片由BGR格式转换为HSV格式,绘制出每一帧图像的直方图,并且利用直方图进行反向投影得到prob图像来突出我们所感兴趣的区域,根据其概率来设置亮度。同时我们也可以通过掩模函数来直接求得感兴趣的区域,将该区域的亮度设置最亮,其他区域设置为暗的。并且利用CamShift算法来实现对感兴趣区域的追踪,最后利用椭圆函数来实现对该区域的画圈。
2. 首先是导入本次实验所用到的库,并且定义一些常量,这个是通过HSV的颜色分量范围来定义的,在后面生成颜色蒙板时需要用到,HSV的颜色范围如下图所示。
之前代码中常量的范围定义有些错误,将颜色范围照着这个表格重新进行设置,该部分代码实现如下:
#导入本次实验所用到的库 import numpy as np import cv2 import video # 定义常量,后面生成颜色蒙板时需要用到,根据HSV的颜色分量范围来定义 LOWER_BLUE = np.array([100., 43., 46.]) UPPER_BLUE = np.array([124., 255., 255.]) LOWER_GREEN = np.array([35., 43., 46.]) UPPER_GREEN = np.array([77., 255., 255.]) LOWER_RED = np.array([0, 43, 46]) UPPER_RED = np.array([10, 255, 255])
3. 接下来定义一些会用到的函数,首先是初始化函数,该部分包含调用摄像头并创建一个对象,定义cv2窗口对话框名称,以及对追踪状态和背景隐藏进行定义。并且对感兴趣的区域和颜色对应起来,默认感兴趣的区域颜色为蓝色,代码实现如下所示。
def __init__(self, color): self.cam = video.create_capture(0) # 捕获摄像头设备并创建一个对象 self.frame = None cv2.namedWindow('camshift') # cv2窗口对话框名称 self.hist_roi = None self.selection = None self.tracking_state = 0 self.hide_background = False # 是否需要隐藏背景,默认显示 if color == 'red': self.flag = 'red' self.roi = cv2.imread('red.jpg') # 读取red.jpg作为region of interest elif color == 'green': self.flag = 'green' self.roi = cv2.imread('green.jpg') # 读取green.jpg作为region of interest else: # detect blue by default self.flag = 'blue' self.roi = cv2.imread('blue.jpg') # 读取blue.jpg作为region of interest
接下来是对start函数进行定义,设定刚开始检测的图像的区域,并且将跟踪检测的值设为1,表示进行跟踪检测。对于掩模函数的定义,主要功能是实现获得hsv_image对应的颜色的蒙板,此处用到在开头定义的hsv颜色分量范围。该部分代码实现如下所示。
def start(self): # 初始化状态参数 self.selection = (0, 0, 640, 480) #选取该区域作为颜色识别检测区域 self.tracking_state = 1 # 是否需要跟踪检测 def get_mask(self, hsv_image, color='blue'): # 获得hsv_image对应颜色的蒙板 if color not in ['blue', 'green', 'red']: return cv2.inRange(hsv_image, np.array([0., 0., 0.]), np.array([255., 255., 255.])) elif color == 'blue': return cv2.inRange(hsv_image, LOWER_BLUE, UPPER_BLUE) elif color == 'green': return cv2.inRange(hsv_image, LOWER_GREEN, UPPER_GREEN) elif color == 'red': return cv2.inRange(hsv_image, LOWER_RED, UPPER_RED)
下面这个是绘制和展示直方图函数,其中用到cv2.rectangle函数,这个函数的作用是在图像上绘制一个简单的矩形。 cv2.rectangle函数定义如下:
Python: cv2.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) → None
Pt1、pt2参数:
import cv2
cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 2)
x1,y1 ------
| |
| |
| |
--------x2,y2
cv2.rectangle 的 pt1 参数和 pt2 参数分别代表矩形的左上角和右下角两个点,而且 x 坐标轴是水平方向的,y 坐标轴是垂直方向的。color 参数一般用 RGB 值指定,表示矩形边框的颜色。thickness 参数表示矩形边框的厚度,如果为负值,则表示填充整个矩形。在本次实验中设为-1,所以是填充整个矩形,在实验中所得到的直方图中我们可以看出来。lineType这个参数看上去是指定 Bresenham 算法是 4 连通的还是 8 连通的。shift 参数表示点坐标中的小数位数。
另外,cv2.cvtColor(img, cv2.COLOR_HSV2BGR)函数是把图像从HSV格式转换为BGR格式,cv2.imshow('hist', img)函数则是把直方图显示出来。代码实现如下所示。
def show_hist(self): # 展示图片的直方图 bin_count = self.hist_roi.shape[0] bin_w = 24 img = np.zeros((256, bin_count * bin_w, 3), np.uint8) for i in xrange(bin_count): h = int(self.hist_roi[i]) cv2.rectangle(img, (i * bin_w + 2, 255), ((i + 1) * bin_w - 2, 255 - h), (int(180.0 * i / bin_count), 255, 255), -1) img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) cv2.imshow('hist', img)
4. 接下来是实验整个运行流程的函数,即run函数,这一部分的代码如下,其中调用的几个函数比较重要,等会详细解释一下。
def run(self): roi = self.roi # 获取ROI self.start() while True: ret, self.frame = self.cam.read() vis = self.frame.copy() #把读取的图像复制一份到vis中 hsv = cv2.cvtColor(self.frame, cv2.COLOR_BGR2HSV) # 将当前帧从RGB格式转换为HSV格式 # 获得当前hsv图像的蒙板 mask = self.get_mask(hsv, color=self.flag) if self.selection: x0, y0, x1, y1 = self.selection self.track_window = (x0, y0, x1, y1) # 追踪子区域 # 对ROI进行颜色格式转换和阈值限制 hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) mask_roi = self.get_mask(hsv_roi, color=self.flag) # 绘制ROI图像的一维直方图 hist_roi = cv2.calcHist([hsv_roi], [0], mask_roi, [16], [0, 180]) # 对hist做归一化 cv2.normalize(hist_roi, hist_roi, 0, 255, cv2.NORM_MINMAX) # 将hist向量reshape为1列并存入self.hist中 self.hist_roi = hist_roi.reshape(-1) self.show_hist() # 可见区域 vis_roi = vis[y0:y1, x0:x1] cv2.bitwise_not(vis_roi, vis_roi) # 对每个像素进行二进制取反操作 # 在vis中,置mask中为0的对应位置也为0 vis[mask == 0] = 0 if self.tracking_state == 1: self.selection = None # 取消ROI模板 prob = cv2.calcBackProject([hsv], [0], self.hist_roi, [0, 180], 1) # 反向投影法 prob &= mask # 与mask进行与运算 得到所求颜色的直方图概率分布 criteria_term = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1) # CamShift算法的停止条件 # 运用CamShift算法对track_window内的图像进行prob检测 track_box, self.track_window = cv2.CamShift(prob, self.track_window, criteria_term) if track_box[1][1] <= 1: # 如果没有检测到 重置检测状态 self.start() else: # 检测到目标颜色 if self.hide_background: # 如果需要隐藏背景, 使用prob直方概率分布图替换vis图像 vis[:] = prob[..., np.newaxis] try: ''' track_box: [[center, axes], [angle, startAngle], endAngle] ''' cv2.ellipse(vis, track_box, (0, 0, 255), 2) # 在track_box内部绘制椭圆图像 print 'center: %.2f\taxes: %.2f\tangle: %.2f\tstart_angle: %.2f\tend_angle: %.2f' % ( track_box[0][0], track_box[0][1], track_box[1][0], track_box[1][1], track_box[2] ) except: print track_box
(1)计算直方图的函数:
cv2:calcHist(images; channels;mask; histSize; ranges[; hist[; accumulate]]),其中:
images: 原图像(图像格式为 uint8 或 float32)。当传入函数时应该用中括号 [] 括起来,例如:[img]。channels:同样需要用中括号括起来,它会告诉函数我们要统计那幅图像的直方图。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是[0], [1],[2]它们分别对应着通道 B,G,R。mask:掩模图像。要统计整幅图像的直方图就把它设为None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。histSize:BIN也就是像素某一灰度值范围内的像素点数目。也应该用中括号括起来,例如:[256]。ranges:像素值范围,通常为 [0,256];注意参数最后 accumulate]]表示 channel、 range 和histSize可以通过中括号写入两个数值,以此来表示两个不同颜色通道。
本次实验中的计算直方图函数为:
cv2.calcHist([hsv_roi], [0], mask_roi, [16], [0, 180])
其中[hsv_roi]为原函数,[0]表示我们只需要处理H通道,mask_roi为掩模图像,H的取值范围四0到180。
(2)反向投影函数:
prob = cv2.calcBackProject([hsv], [0], self.hist_roi, [0, 180], 1)
反向投影函数其实是先划分好区域,然后统计出区域内的像素点的个数,并且绘制出图像直方图,我们可以将每个区域内的像素点个数归一化到[0,255]区间范围内,然后我们将原图像中相应区域的像素值换成我们统计出来的并且已经归一化到[0,255]区间内的值,这样某个区域内的像素点越多,反向投影那个区域机就会越亮。比如我们此次实验中选择的感兴趣的区域为蓝色,测试图片也全是蓝色的,那么我们方向投影出来蓝色区域的值都为255,其他颜色区域基本接近于0,就可以突出显示蓝色区域。
另外在此次实验中我们还用到了另一种方法,即掩模方法,掩模的话是直接将我们感兴趣的区域的像素值设置为255,而其他不感兴趣的区域的像素值则设置为0,我们可以看到掩模图像中感兴趣区域是亮的,而其他区域则是暗的。
(3)camshift函数:
criteria_term = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1) # CamShift算法的停止条件 # 运用CamShift算法对track_window内的图像进行prob检测 track_box, self.track_window = cv2.CamShift(prob, self.track_window, criteria_term)
Camshift函数的原型为:RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)。
其中probImage为输入图像直方图的反向投影图,window为要跟踪目标的初始位置矩形框,criteria为算法结束条件。函数返回一个有方向角度的矩阵。该函数的实现首先是利用meanshift算法计算出要跟踪的中心,然后调整初始窗口的大小位置和方向角度。在camshift内部调用了meanshift算法计算目标的重心。
基本思想是以视频图像中运动物体的颜色信息作为特征,对输入图像的每一帧分贝做Mean-Shift运算,并将上一针的目标中心与搜索窗口大小作为下一帧Mean-Shift算法的中心和搜索窗口大小的初始值,如此迭代下去,就可以实现对目标的跟踪。
Mean-Shift迭代过程:首先在颜色概率分布图中(反向投影得到)中选择搜索窗口的大小和初始位置,然后计算搜索窗口的质心位置。设像素点(i,j)位于搜索窗口内,I(i,j)是颜色直方图的反向投影图中该像素点对应的值,定义搜索窗口的零阶M00和一阶矩M10, M01如下:
则搜索窗口的质心位置为:(M10/M00, M01/M00)。接着调整搜索窗口中心到质心。零阶矩反映了搜索窗口的尺寸,依据它调整窗口的大小,并将搜索窗口的中心移到质心,如果移动距离大于设定的阈值,则重新计算调整后的窗口质心,进行新一轮的窗口位置和尺寸调整。直到窗口中心与质心之间的移动距离小于阈值,或者迭代次数达到某一最大值,认为收敛条件满足,将搜索窗口位置和大小作为下一帧的目标位置输入,开始对下一帧图像进行新的目标搜索。
CamShift算法跟踪流程图如下:
(4)绘制椭圆图像函数
cv2.ellipse(vis, track_box, (0, 0, 255), 2) #在track_box内部绘制椭圆图像
通过参考图片颜色概率的反向投影函数在图像中找到目标颜色,通过camshift函数得到目标颜色在帧中的坐标信息并存入track_box,持续跟踪该物体。通过cv2.ellipse 画椭圆将图中的目标物体标识出来,参数中track_box为目标颜色坐标值,(0,0,255)选择BGR模式中的颜色,2为椭圆线圈像素宽度。
当目标物体变得太远或移出画面时,track_box值将无法计算,判断track_box数值小于1时,需要重置算法,重新搜索并跟踪。
五、运行结果
运行结果分析:
在本次实验中,我分别选择了蓝色和绿色部分作为感兴趣区域,上面的截图中都有包含颜色直方图、原图像经过掩模过后生成的mask图像、由直方图反向映射得到的prob图像以及由mask图像和prob图像相与得到的mask&prob图像。掩模和反向映射其实是两种不同的方法,目标都是使得我们感兴趣的区域变得亮一些,不感兴趣的区域则为暗的。在后面的CamShift算法中会用到反向映射后生成的图像,可以用来选择搜索窗口的大小和初始位置,将通过camshift函数得到目标颜色在帧中的坐标信息并存入track_box,持续跟踪该物体。然后再通过cv2.ellipse函数画椭圆将图中的目标物体标识出来,即得到图中的CamShift图像
六、问题回答
1.什么是图像的直方图?
直方图是对数据的集合统计,并将统计结果分布于一系列预定义的bins 中。这里的数据不仅仅指的是灰度值,统计数据可能是任何能有效描述图像的特征 (如梯度, 方向等等)。
灰度图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此一张较暗图片的直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。CV 领域常借助图像直方图来实现图像的二值化。
彩色图像直方图和灰度图像直方图的原理是一样的,不同的是彩色图像需要分别计算BGR三个通道。
2.HSV 空间通过哪几个维度表达颜色分布?
色调H:用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°, 品红为300°;
饱和度S:饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
明暗度V:明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。
HSV色彩空间如下图所示,用一个倒圆锥体表示整个色彩空间。
备注: 该文章为课程实验报告,有的地方可能并不全面,仅供参考,希望对大家有一些帮助,当然请不要直接复制文章内容。