opencv的颜色跟踪学习

一.主要代码分析

1.首先是实例化模板,默认检测为蓝色

if __name__ == '__main__':
    import sys
    try: color = sys.argv[1] #外部读入数据
    except: color = 1 #在函数发生IndexError的时候,直接赋值给color为1,检测颜色为蓝色
    print __doc__
    a = App(color) #将a定义为APP的实例
    a.run() #使用a的方法“run”

2.运行run函数方法,首先设定roi,其中,roi为蓝图片大小

def run(self):
        roi = self.roi
        self.start()
        while True:
            ret, self.frame = self.cam.read() #获得每一帧图片
            ...
            hsv = cv2.cvtColor(self.frame, cv2.COLOR_BGR2HSV) #由于在HSV颜色空间中要比在BGR空间中更容易表示一个特定的颜色,将图像转换到HSV空间

3.转换为2D直方图,包括输入图像和掩膜图像的直方图,利用反向投影计算,它会输出与输入图像(待搜索)同样大小的图像,其中每一个像素代表了输入图像上对应点属于目标对象的概率。用更简单的话来解释,输出图像中像素值越高(越白)的点就越可能代表我们要搜索的目标(在输入图像所在的位置)。

#一维直方图 直方图统计区域,ranges像素值范围是:[0,256],histSize即BIN的数目:[16]
hist = cv2.calcHist( [hsv_roi], [0], mask_roi, [16], [0, 180] )
# 归一化:原始图像,结果图像,映射到结果图像中的最小值,最大值,归一化类型
#归一化之后的直方图便于显示,归一化之后就成了 0 到 255 之间的数了。
cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
...
#直方图反向投影。它的参数与函数 cv2.calcHist 的参数基本相同。其中的一个参数是我们要查找目标的直方图。同样再使用目标的直方图做反向投影之前我们应该先对其做归一化处理。返回的结果是一个概率图像,我们再使用一个圆盘形卷积核对其做卷操作,最后使用阈值进行二值化
prob = cv2.calcBackProject([hsv], [0], self.hist, [0, 180], 1)
prob &= mask
#CamShift返回的是一个带旋转角度的矩形,以及这个矩形的参数(被用到下一次迭代过程中),这个算法首先要使用 meanshift, meanshift 找到(并覆盖)目标之后,再去调整窗口的大小, 它还会计算目标对象的最佳外接椭圆的角度,并以此调节窗口角度。然后使用更新后的窗口大小和角度来在原来的位置继续进行 meanshift。重复这个过程知道达到需要的精度。
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
track_box, self.track_window = cv2.CamShift(prob, self.track_window, term_crit)
#而Meanshift 算法的基本原理是和很简单的。假设我们有一堆点(比如直方图反向投影得到的点),和一个小的圆形窗口,我们要完成的任务就是将这个窗口移动到最大灰度密度处(或者是点最多的地方)。将新窗口的中心移动到新的质心。就这样不停的迭代操作直到窗口的中心和其所包含点的质心重合为止(或者有一点小误差)。

二。原理理解

1.按位运算

在想把一张图片和另外一张图片合并的时候,如果使用加法,颜色会改变,如果使用混合,会得到透明的效果,但是不想要透明,如果它是矩形可以采用ROI,但是不是矩形,可以采用下面的按位运算实现。

# 加载图像
img1 = cv2.imread('roi.jpg')
img2 = cv2.imread('opencv_logo.png')

# 获得图片的高,宽,通道数。设置ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]

#取 roi 中与 mask 中不为零的值对应的像素的值,其他值为 0
#注意这里必须有 mask=mask 或者 mask=mask_inv, 其中的 mask= 不能忽略
img1_bg = cv2.bitwise_and(roi,roi,mask = mask)

# 取 roi 中与 mask_inv 中不为零的值对应的像素的值,其他值为 0。
img2_fg = cv2.bitwise_and(img2,img2,mask = mask_inv)

# Put logo in ROI and modify the main image
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst

2.直方图

直方图的 x 轴是灰度值( 0 到 255), y 轴是图片中具有同一个灰度值的点的数目。直方图其实就是对图像的另一种解释。通过直方图我们可以对图像的对比度,亮度,灰度分布等有一个直观的认识。

  • BINS:直方图显示了每个灰度值对应的像素数。如果像素值为 0到 255,你就需要 256 个数来显示上面的直方图。但是,如果你不需要知道每一个像素值的像素点数目的,而只希望知道两个像素值之间的像素点数目怎么办呢?举例来说,我们想知道像素值在 0 到 15 之间的像素点的数目,接着是 16 到 31,…, 240 到 255。我们只需要 16 个值来绘制直方图。
  • DIMS:表示我们收集数据的参数数目。在本例中,我们对收集到的数据只考虑一件事:灰度值。所以这里就是 1
  • RANGE:就是要统计的灰度值范围,一般来说为 [0, 256],也就是说所有的灰度值
  • mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。
img = cv2.imread('home.jpg',0)
# 别忘了中括号 [img],[0],None,[256],[0,256],只有 mask 没有中括号
#cv2:calcHist(images,channels,mask,histSize,ranges[hist[,accumulate]])
hist = cv2.calcHist([img],[0],None,[256],[0,256])

要统计图像某个局部区域的直方图只需要构建一副掩模图像。将要统计的部分设置成白色,其余部分为黑色,就构成了一副掩模图像。然后把这个掩模图像传给函数就可以了。

mg = cv2.imread('home.jpg',0)
# 建立掩膜
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)
# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

3.直方图反向投影

直方图反向投影:可以用来做图像分割,或者在图像中找寻我们感兴趣的部分。简单来说,它会输出与输入图像(待搜索)同样大小的图像,其中的每一个像素值代表了输入图像上对应点属于目标对象的概率。用更简单的话来解释,输出图像中像素值越高(越白)的点就越可能代表我们要搜索的目标(在输入图像所在的位置)。这是一个直观的解释。直方图投影经常与 camshift算法等一起使用。

  • 首先,我们要创建两幅颜色直方图,目标图像的直方图( ‘M’),(待搜索)
    输入图像的直方图( ‘I’)。
#roi is the object or region of object we need to find
roi = cv2.imread('rose_red.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
#target is the image we search in
target = cv2.imread('rose.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)
# Find the histograms using calcHist. Can be done with np.histogram2d also
M = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv2.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )
  • 计算比值:R=MIR=\frac{M}{I} 反向投影 R,也就是根据 R 这个”调色板“创建一副新的图像,其中的每一个像素代表这个点就是目标的概率。例如 B(x,y)=R[h(x,y),s(x,y)]B(x,y) = R[h(x,y),s(x,y)] 其中 h 为点( x, y)处的 hue 值, s 为点( x, y)处的saturation 值。最后加入再一个条件 B (x,y) = min [B (x,y),1]。
h,s,v = cv2.split(hsvt)
B = R[h.ravel(),s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])
  • 现在使用一个圆盘算子做卷积, B = D × B,其中 D 为卷积核
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
B=cv2.filter2D(B,-1,disc)
B = np.uint8(B)
cv2.normalize(B,B,0,255,cv2.NORM_MINMAX)

现在输出图像中灰度值最大的地方就是我们要查找的目标的位置了。如果
我们要找的是一个区域,我们就可以使用一个阈值对图像进行二值化,这样就可以得到一个很好的结果了。

遇到的问题以及最后结果

  • 由于opencv版本问题,python2不支持matplotlib,而本程序中并未使用matplotlib,因此删除matplotlib.

  • cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, w)
    cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, h)
    两段代码不适合opencv的3代版本,替换为:
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, w)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, h)

  • 实验结果

opencv的颜色跟踪学习