GUI操作

图片

读取/显示

  • cv2.IMREAD_COLOR:读入一副彩色图像,图像的透明度会被忽略,这是默认参数
  • cv2.IMREAD_GRAYSCALE:以灰度模式读入图像
  • cv2.IMREAD_UNCHANGED:读入一幅图像,并且包括图像的 alpha ```python import cv2

image1 = cv2.imread(‘./sample.jpg’, 1) image2 = cv2.imread(‘./sample.jpg’, 0) image3 = cv2.imread(‘./sample.jpg’, -1)

cv2.imshow(‘flags=1’, image1) cv2.imshow(‘flags=0’, image2) cv2.imshow(‘flags=-1’, image3)

cv2.waitKey(0) cv2.destroyAllWindows()

  1. > 注意:彩色图像使用 OpenCV 加载时是 BGR 模式。但是 Matplotib RGB 模式。所以彩色图像如果已经被 OpenCV 读取,那它将不会被 Matplotib 正确显示。
  2. <a name="b68ug"></a>
  3. ### 保存
  4. ```python
  5. import cv2
  6. image = cv2.imread('./sample.jpg', 1)
  7. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  8. cv2.imwrite('messigray.png', gray)

视频

读取

  1. import cv2
  2. from time import sleep
  3. name = 'sample'
  4. cap = cv2.VideoCapture('./test.avi')
  5. while cap.isOpened():
  6. sleep(0.1)
  7. ret, frame = cap.read()
  8. cv2.waitKey(10)
  9. cv2.imshow(name, frame)
  10. if cv2.getWindowProperty(name, cv2.WND_PROP_AUTOSIZE) < 1:
  11. # 点x退出
  12. break
  13. cap.release()
  14. cv2.destroyAllWindows()

cap.get(propId)

  1. CAP_PROP_POS_MSEC = 0 #视频文件的当前位置(毫秒)
  2. CAP_PROP_POS_FRAMES = 1 #基于帧的索引,下一步解码/捕获。
  3. CAP_PROP_POS_AVI_RATIO = 2 #视频文件的相对位置:0 -开始,1 -结束
  4. CAP_PROP_FRAME_WIDTH = 3 #视频流中帧的宽度
  5. CAP_PROP_FRAME_HEIGHT = 4 #视频流中帧的高度
  6. CAP_PROP_FPS = 5 #帧频
  7. CAP_PROP_FOURCC = 6 #编解码器的四字符代码
  8. CAP_PROP_FRAME_COUNT = 7 #视频文件中的帧数
  9. CAP_PROP_FORMAT = 8 #retrieve()返回的Mat对象的格式
  10. CAP_PROP_MODE = 9 #后端特定值,指示当前捕获模式
  11. CAP_PROP_BRIGHTNESS = 10 #图像亮度(仅适用于相机)
  12. CAP_PROP_CONTRAST = 11 #图像的对比度(仅适用于相机)
  13. CAP_PROP_SATURATION = 12 #图像饱和度(仅适用于相机)
  14. CAP_PROP_HUE = 13 #图像色调(仅供相机使用)
  15. CAP_PROP_GAIN = 14 #图像的增益(仅适用于相机)
  16. CAP_PROP_EXPOSURE = 15 #曝光(仅对相机)
  17. CAP_PROP_CONVERT_RGB = 16 #布尔标志,指示图像是否应该转换为RGB
  18. CAP_PROP_WHITE_BALANCE_BLUE_U = 17 #目前不支持的
  19. CAP_PROP_RECTIFICATION = 18 #立体声摄像机校正标志(注意:目前仅支持DC1394 v2.x后端)
  20. ...

其中的一些值可以用 cap.set(propId,value) 来修改,value 就是你想要设置的新值。

保存

FourCC 就是一个 4 字节码,用来确定视频的编码格式。

  • Windows: DIVX
  • OSX: cv2.VideoWriter_fourcc(*'MJPG')cv2.cv.FOURCC('M','J','P','G')cv2.cv.FOURCC(*'MJPG')

绘图

  1. cv2.line(),cv2.circle(),cv2.rectangle(),cv2.ellipse(),cv2.putText()
  • img:想要绘制图形的那幅图像。
  • color:形状的颜色。以 RGB 为例,需要传入一个元组,例如:(255,0,0)代表蓝色。对于灰度图只需要传入灰度值。
  • thickness:线条的粗细。如果给一个闭合图形设置为 -1,那么这个图形就会被填充。默认值是 1.
  • linetype:线条的类型,连接,抗锯齿等。默认情况是 8 连接。cv2.LINE_AA为抗锯齿,这样看起来会非常平滑。 ```python import numpy as np import cv2 as cv

Create a black image

img = np.zeros((512,512,3), np.uint8)

Draw a diagonal blue line with thickness of 5 px

cv.line(img,(0,0),(511,511),(255,0,0),5)

cv2.rectangle(img,(384,0),(510,128),(0,255,0),3)

cv2.circle(img,(447,63),63,(0,0,255),-1)

cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1)

pts=np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)

这里 reshape 的第一个参数为-1, 表明这一维的长度是根据后面的维度计算出来的。

pts=pts.reshape((-1,1,2))

可以被用来画很多条线。只需要把要画的线放在一 个列表中,将这个列表传给函数就可以了。每条线都会被独立绘制。这会比用 cv2.line() 一条一条的绘制要快一些。

cv2.polylines()

font=cv2.FONT_HERSHEY_SIMPLEX cv2.putText(img,’OpenCV’,(10,500), font, 4,(255,255,255),2)

  1. <a name="giL7g"></a>
  2. # 核心操作
  3. <a name="pzOvw"></a>
  4. ## 基础
  5. <a name="2zYQp"></a>
  6. ### 属性
  7. 图像的属性包括:行(高)、列(宽)、通道、图像数据类型、像素数目等。
  8. - img.shape 获取图像的形状。返回值是一个包含行数,列数,通道数的元组。
  9. - img.size 获取图像的像素数目( 宽*高*通道数,即shape元素的乘积 )。
  10. - img.dtype 获取图像的数据类型.
  11. ```python
  12. import cv2
  13. img = cv2.imread('./images/roi.jpg')
  14. print(img.shape)
  15. print(img.shape[:2][::-1])
  16. print(img.size)
  17. print(img.dtype)
  18. # (280, 450, 3)
  19. # (450, 280)
  20. # 378000
  21. # uint8

颜色转化

  1. import cv2
  2. image = cv2.imread('./sample.jpg', 1)
  3. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  4. cv2.imshow('sample', gray)
  5. cv2.waitKey(0)
  6. cv2.destroyAllWindows()

修改像素

Numpy 是经过优化了的进行快速矩阵运算的软件包。所以我们不推荐逐个获取像素值并修改,这样会很慢,能用矩阵运算就不要用循环。

  1. import cv2
  2. import numpy as np
  3. img = cv2.imread('./images/roi.jpg')
  4. print(img.item(10,10,2))
  5. img.itemset((10,10,2),100)
  6. print(img.item(10,10,2))
  7. # 50
  8. # 100

ROI

  1. import cv2
  2. img = cv2.imread('./images/roi.jpg')
  3. ball = img[280:340,330:390]
  4. img[273:333,100:160] = ball

拆分及合并图像通道

  1. import cv2
  2. img = cv2.imread('./images/roi.jpg')
  3. b,g,r = cv2.split(img)
  4. #b = img[:,:,0]
  5. img = cv2.merge(b, g, r)

为图像扩边(填充)

  1. import cv2 as cv
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. BLUE = [255,0,0]
  5. img1 = cv.imread('opencv-logo.png')
  6. replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
  7. reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
  8. reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
  9. wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
  10. constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)
  11. plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
  12. plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
  13. plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
  14. plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
  15. plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
  16. plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
  17. plt.show()

image.png

算数运算

图像加法

  1. import cv2
  2. import numpy as np
  3. x = np.uint8([250])
  4. y = np.uint8([10])
  5. print (cv2.add(x,y)) # 250+10 = 260 => 255
  6. [[255]]
  7. print (x+y) # 250+10 = 260 % 256 = 4
  8. [4]

图像混合

图像混合的计算公式:
OpenCV1 - 图3

通过修改 α 的值(0 → 1),可以实现非常酷的混合:
OpenCV1 - 图4

  1. img1 = cv.imread('ml.png')
  2. img2 = cv.imread('opencv-logo.png')
  3. dst = cv.addWeighted(img1,0.7,img2,0.3,0)
  4. cv.imshow('dst',dst)
  5. cv.waitKey(0)
  6. cv.destroyAllWindows()

image.png

按位运算

这里包括的按位操作有:AND, OR, NOT, XOR 等。

性能及优化

cv2.getTickCount 函数返回从参考点到这个函数被执行的时钟数。所以当你在一个函数执行前后都调用它的话,你就会得到这个函数的执行时间(时钟数)。

cv2.getTickFrequency 返回时钟频率,或者说每秒钟的时钟数。所以你可以按照下面的方式得到一个函数运行了多少秒:

  1. import cv2
  2. e1 = cv2.getTickCount()
  3. e2 = cv2.getTickCount()
  4. time = (e2 - e1)/ cv2.getTickFrequency()

OpenCV 中的很多函数都被优化过(使用 SSE2, AVX 等)。也包含一些没有被优化的代码。如果我们的系统支持优化的话要尽量利用只一点。在编译时优化是被默认开启的。因此 OpenCV 运行的就是优化后的代码,如果你把优化关闭的话就只能执行低效的代码了。你可以使用函数 cv2.useOptimized() 来查看优化是否被开启了,使用函数 cv2.setUseOptimized() 来开启优化。

图像处理

颜色空间

颜色转换

在 OpenCV 中有超过 150 种进行颜色空间转换的方法。但是你会发现我们经常用到的也就两种:BGR↔GrayBGR↔HSV

  1. #flag:
  2. #cv2.COLOR_BGR2GRAY
  3. #cv2.COLOR_BGR2HSV
  4. cv2.cvtColor(input_image, ag)

在 OpenCV 的 HSV 格式中: H(色彩/色度)的取值范围是 [0,179] S(饱和度)的取值范围 [0,255] V(亮度)的取值范围 [0,255] 但是不同的软件使用的值可能不同。所以当你需要拿OpenCV的HSV值与其他软件的 HSV 值进行对比时,一定要记得归一化。

物体跟踪

现在我们知道怎样将一幅图像从 BGR 转换到 HSV 了,我们可以利用这 一点来提取带有某个特定颜色的物体。在 HSV 颜色空间中要比在 BGR 空间中更容易表示一个特定颜色:

  • 从视频中获取每一帧图像。
  • 将图像转换到 HSV 空间。
  • 设置 HSV 阈值到蓝色范围。
  • 获取蓝色物体,在蓝色物体周围画一个圈。

几何变换

OpenCV 提供了两个变换函数:cv2.warpAffinecv2.warpPerspective,使用这两个函数你可以实现所有类型的变换。cv2.warpAffine 接收的参数是 2 × 3 的变换矩阵,而 cv2.warpPerspective 接收的参数是 3 × 3 的变换矩阵。

缩放

扩展缩放只是改变图像的尺寸大小。OpenCV 提供的函数 cv2.resize() 可以实现这个功能。图像的尺寸可以自己手动设置,也可以指定缩放因子。我们可以选择使用不同的插值方法。在缩放时推荐使用 cv2.INTER_AREA,在扩展时推荐使用 cv2.INTER_CUBIC(慢) 和 cv2.INTER_LINEAR

平移

平移就是将对象换一个位置。如果你要沿 (x, y) 方向移动,移动的距离是 (tx,ty),可以按照下面的方式构建移动矩阵:

OpenCV1 - 图6

旋转

对一个图像旋转角度 θ, 需要使用到下面形式的旋转矩阵:

OpenCV1 - 图7

仿射

在仿射变换中,原图中所有的平行线在结果图像中同样平行。为了创建这个矩阵我们需要从原图像中找到三个点以及他们在输出图像中的位置。然后 cv2.getAffineTransform 会创建一个 2x3 的矩阵,最后这个矩阵会被传给函数 cv2.warpAffine

透视

对于视角变换,我们需要一个 3x3 变换矩阵。在变换前后直线还是直线。要构建这个变换矩阵,你需要在输入图像上找 4 个点,以及他们在输出图像上对应的位置。这四个点中的任意三个都不能共线。这个变换矩阵可以由函数 cv2.getPerspectiveTransform() 构建。然后把这个矩阵传给函数 cv2.warpPerspective

阀值

单一

当像素值高于阈值时,我们给这个像素 赋予一个新值(可能是白色),否则我们给它赋予另外一种颜色(也许是黑色)。这个函数就是 cv2.threshhold()。这个函数的第一个参数就是原图像,原图像应该是灰度图;第二个参数就是用来对像素值进行分类的阈值;第三个参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值。

  1. import cv2 as cv
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv.imread('gradient.png',0)
  5. ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
  6. ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
  7. ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
  8. ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
  9. ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
  10. titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
  11. images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
  12. for i in xrange(6):
  13. plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
  14. plt.title(titles[i])
  15. plt.xticks([]),plt.yticks([])
  16. plt.show()

image.png

自适应

在前面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。

  1. import cv2 as cv
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv.imread('sudoku.png',0)
  5. img = cv.medianBlur(img,5)
  6. ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
  7. th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
  8. cv.THRESH_BINARY,11,2)
  9. th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
  10. cv.THRESH_BINARY,11,2)
  11. titles = ['Original Image', 'Global Thresholding (v = 127)',
  12. 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
  13. images = [img, th1, th2, th3]
  14. for i in xrange(4):
  15. plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
  16. plt.title(titles[i])
  17. plt.xticks([]),plt.yticks([])
  18. plt.show()

image.png

Otsu

当我们使用 Otsu 二值化时会用到 retVal。在使用全局阈值时,我们就是随便给了一个数来做阈值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。如果是一副双峰图像(简 单来说双峰图像是指图像直方图中存在两个峰)呢?我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?这就是 Otsu 二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)。

  1. import cv2 as cv
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv.imread('noisy2.png',0)
  5. # global thresholding
  6. ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
  7. # Otsu's thresholding
  8. ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
  9. # Otsu's thresholding after Gaussian filtering
  10. blur = cv.GaussianBlur(img,(5,5),0)
  11. ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
  12. # plot all the images and their histograms
  13. images = [img, 0, th1,
  14. img, 0, th2,
  15. blur, 0, th3]
  16. titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
  17. 'Original Noisy Image','Histogram',"Otsu's Thresholding",
  18. 'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
  19. for i in xrange(3):
  20. plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
  21. plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
  22. plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
  23. plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
  24. plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
  25. plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
  26. plt.show()

image.png

平滑(模糊)

2D卷积过滤

与一维信号一样,还可以使用各种低通滤波器(LPF),高通滤波器(HPF)等对图像进行滤波。LPF有助于消除噪声,使图像模糊等。HPF滤波器有助于在图像中找到边缘。

模糊

平均

这是由一个归一化卷积框完成的。他只是用卷积框覆盖区域所有像素的平均值来代替中心元素。可以使用函数 cv2.blur()cv2.boxFilter() 来完这个任务。

高斯

现在把卷积核换成高斯核(简单来说,方框不变,将原来每个方框的值是相等的,现在里面的值是符合高斯分布的,方框中心的值最大,其余方框根据距离中心元素的距离递减,构成一个高斯小山包。原来的求平均数现在变成求加权平均数,全就是方框里的值)。实现的函数是 cv2.GaussianBlur()

中值

顾名思义就是用与卷积框对应像素的中值来替代中心像素的值。这个滤波器经常用来去除椒盐噪声。前面的滤波器都是用计算得到的一个新值来取代中心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代它。 它能有效的去除噪声。卷积核的大小也应该是一个奇数。

双边滤波

函数 cv2.bilateralFilter()能在保持边界清晰的情况下有效的去除噪音。但是这种操作与其他滤波器相比会比较慢。我们已经知道高斯滤波器是求中心点邻近区域像素的高斯加权平均值。这种高斯滤波器只考虑像素之间的空间关系,而不会考虑像素值之间的关系(像素的相似度)。所以这种方法不会考虑一个像素是否位于边界。因此边界也会别模糊掉,而这正不是我们想要。

形态学转换

image.png

腐蚀

就像土壤侵蚀一样,这个操作会把前景物体的边界腐蚀掉(但是前景仍然是白色)。
image.png

膨胀

与腐蚀相反,与卷积核对应的原图像的像素值中只要有一个是 1,中心元素的像素值就是 1。所以这个操作会增加图像中的白色区域(前景)。一般在去噪声时先用腐蚀再用膨胀。因为腐蚀在去掉白噪声的同时,也会使前景对象变 小。所以我们再对他进行膨胀。这时噪声已经被去除了,不会再回来了,但是前景还在并会增加。膨胀也可以用来连接两个分开的物体。
image.png

开运算

先进性腐蚀再进行膨胀就叫做开运算。就像我们上面介绍的那样,它被用来去除噪声。这里我们用到的函数是 cv2.morphologyEx()
image.png

闭运算

先膨胀再腐蚀。它经常被用来填充前景物体中的小洞,或者前景物体上的小黑点。
image.png

梯度

其实就是一幅图像膨胀与腐蚀的差别。 结果看上去就像前景物体的轮廓。
image.png

Top Hat

原始图像与进行开运算之后得到的图像的差。下面的例子是用一个 9x9 的核进行礼帽操作的结果。image.png

Black Hat

进行闭运算之后得到的图像与原始图像的差。
image.png

Canny边缘检测

边缘检测是图像处理和计算机视觉的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点,图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括:深度上的不连续,表面方向的不连续,物质属性变化和场景照明变化。

  1. import numpy as np
  2. import cv2 as cv
  3. from matplotlib import pyplot as plt
  4. img = cv.imread('messi5.jpg',0)
  5. edges = cv.Canny(img,100,200)
  6. plt.subplot(121),plt.imshow(img,cmap = 'gray')
  7. plt.title('Original Image'), plt.xticks([]), plt.yticks([])
  8. plt.subplot(122),plt.imshow(edges,cmap = 'gray')
  9. plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
  10. plt.show()

image.png

图像金字塔

一般情况下,我们要处理是一副具有固定分辨率的图像。但是有些情况下,我们需要对同一图像的不同分辨率的子图像进行处理。比如,我们要在一幅图像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸大小。这种情况 下,我们需要创建创建一组图像,这些图像是具有不同分辨率的原始图像。我们把这组图像叫做图像金字塔(简单来说就是同一图像的不同分辨率的子图集合)。如果我们把最大的图像放在底部,最小的放在顶部,看起来像一座金字 塔,故而得名图像金字塔。
image.png

轮廓

轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。

绘制

  1. import cv2
  2. im = cv2.imread('test.jpg')
  3. imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
  4. ret,thresh = cv2.threshold(imgray,127,255,0)
  5. image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
  6. img = cv2.drawContour(img, contours, -1, (0,255,0), 3)
  7. img = cv2.drawContours(img, contours, 3, (0,255,0), 3)

轮廓的近似

特征

图像的矩可以用于计算图像的质心,面积等。

面积

轮廓的面积可以使用函数 cv2.contourArea() 计算得到,也可以使用矩(0 阶矩), M['m00']

周长

也被称为弧长。可以使用函数 cv2.arcLength() 计算得到。

近似

将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。

下边,第二幅图中的绿线是当epsilon = 10% 时得到的近似轮廓,第三幅图是当 epsilon = 1% 时得到的近似轮廓。第三个参数设定弧线是否闭合。
image.png

凸包

凸包与轮廓近似相似,但不同,虽然有些情况下它们给出的结果是一样的。函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。例如下图中的手。红色曲线显示了手的凸包,凸性缺陷被双箭头标出来了。
image.png

凸性检测

函数 cv2.isContourConvex() 可以可以用来检测一个曲线是不是凸的。

边界矩形

直边界矩形,一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。 所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 查找得到。(x, y)为矩形左上角的坐标,(w, h)是矩形的宽和高。

旋转的边界矩形,这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为 cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标(x, y),矩形的宽和高(w, h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。
image.png

最小外接圆

函数 cv2.minEnclosingCircle() 可以帮我们找到一个对象的外切圆。它是所有能够包括对象的圆中面积最小的一个。
image.png

椭圆拟合

使用的函数为cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。
image.png

直线拟合

我们也可以为图像中的白色点拟合出一条直线。
image.png

属性

纵横比

范围

固体度

等效直径

方向

掩模和像素点

最大值/最小值/位置

平均颜色/灰度

极点

一个对象最上面,最下面,最左边,最右边的点。

  1. leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
  2. rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
  3. topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
  4. bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

image.png

方法

凸缺陷

轮廓的凸包对象上的任何凹陷都被成为凸缺陷。OpenCV 中有一个函数 cv2.convexityDefect() 可以帮助我们找到凸缺陷。函数调用如下:

  1. hull = cv2.convexHull(cnt,returnPoints = False)
  2. defects = cv2.convexityDefects(cnt,hull)
  1. import cv2 as cv
  2. import numpy as np
  3. img = cv.imread('star.jpg')
  4. img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  5. ret,thresh = cv.threshold(img_gray, 127, 255,0)
  6. contours,hierarchy = cv.findContours(thresh,2,1)
  7. cnt = contours[0]
  8. hull = cv.convexHull(cnt,returnPoints = False)
  9. defects = cv.convexityDefects(cnt,hull)
  10. for i in range(defects.shape[0]):
  11. s,e,f,d = defects[i,0]
  12. start = tuple(cnt[s][0])
  13. end = tuple(cnt[e][0])
  14. far = tuple(cnt[f][0])
  15. cv.line(img,start,end,[0,255,0],2)
  16. cv.circle(img,far,5,[0,0,255],-1)
  17. cv.imshow('img',img)
  18. cv.waitKey(0)
  19. cv.destroyAllWindows()

image.png

多边形点测试

求解图像中的一个点到一个对象轮廓的最短距离。如果点在轮廓的外部, 返回值为负;如果在轮廓上,返回值为 0;如果在轮廓内部,返回值为正。

  1. dist = cv2.pointPolygonTest(cnt, (50, 50), True)

形状匹配

函数 cv2.matchShape() 可以帮我们比较两个形状或轮廓的相似度。如果返回值越小,匹配越好。它是根据 Hu 矩值来计算的。

层次结构

通常我们使用函数 cv2.findContours 在图片中查找一个对象。有时对象可能位于不同的位置。还有些情况,一个形状在另外一个形状的内部。这种情况下我们称外部的形状为父,内部的形状为子。按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就成为组织结构。
image.png

直方图

通过直方图你可以对整幅图像的灰度分布有一个整体的了解。直方图的 x 轴是灰度值(0 到 255),y 轴是图片中具有同一个灰度值的点的数目。

统计

函数 cv2.calcHist 可以帮助我们统计一幅图像的直方图。我们一起来熟悉一下这个函数和它的参数:

  1. cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
  1. images: 原图像(图像格式为 uint8 或 float32)。当传入函数时应该用中括号 [] 括起来,例如:[img]。
  2. channels: 同样需要用中括号括起来,它会告诉函数我们要统计那幅图像的直方图。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是 [0], [1], [2] 它们分别对应着通道 B, G, R。
  3. mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。
  4. histSize:BIN 的数目。也应该用中括号括起来,例如:[256]。
  5. ranges: 像素值范围,通常为 [0,256]

绘制

  1. import cv2
  2. from matplotlib import pyplot as plt
  3. img = cv2.imread('home.jpg',0)
  4. plt.hist(img.ravel(),256,[0,256])
  5. plt.show()

image.png

掩模

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

直方图均衡化

如果一副图像中的大多是像素点的像素值都集中在一个像素值范围之内会怎样呢?例如,如果一幅图片整体很亮,那所有的像素值应该都会很高。但是一副高质量的图像的像素值分布应该很广泛。所以你应该把它的直方图做一个横向拉伸(如下图),这就是直方图均衡化要做的事情。通常情况下这种操作会改善图像的对比度。
image.png

直方图均衡化函数为 cv2.equalizeHist()。这个函数的输入图片仅仅是一副灰度图像,输出结果是直方图均衡化之后的图像。

  1. img = cv2.imread('wiki.jpg',0)
  2. equ = cv2.equalizeHist(img)
  3. res = np.hstack((img,equ))
  4. #stacking images side-by-side
  5. cv2.imwrite('res.png',res)

image.png

2D直方图

在前面的部分我们介绍了如何绘制一维直方图,之所以称为一维,是因为我们只考虑了图像的一个特征:灰度值。但是在 2D 直方图中我们就要考虑两个图像特征。对于彩色图像的直方图通常情况下我们需要考虑每个的颜色(Hue)和饱和度(Saturation)。根据这两个特征绘制 2D 直方图。

直方图反向投影

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

  1. import numpy as np
  2. import cv2 as cv
  3. roi = cv.imread('rose_red.png')
  4. hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
  5. target = cv.imread('rose.png')
  6. hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
  7. # calculating object histogram
  8. roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
  9. # normalize histogram and apply backprojection
  10. cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
  11. dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
  12. # Now convolute with circular disc
  13. disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
  14. cv.filter2D(dst,-1,disc,dst)
  15. # threshold and binary AND
  16. ret,thresh = cv.threshold(dst,50,255,0)
  17. thresh = cv.merge((thresh,thresh,thresh))
  18. res = cv.bitwise_and(target,thresh)
  19. res = np.vstack((target,thresh,res))
  20. cv.imwrite('res.jpg',res)

使用图中蓝色矩形中的区域作为取样对象, 再根据这个样本搜索图中所有的类似区域(草地)。
image.png

傅立叶变换

傅里叶变换经常被用来分析不同滤波器的频率特性。我们可以使用 2D 离散傅里叶变换 (DFT) 分析图像的频域特性。

模版匹配

模板匹配是用来在一副大图中搜寻查找模版图像位置的方法。OpenCV 为我们提供了函数:cv2.matchTemplate()

  1. import cv2 as cv
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img_rgb = cv.imread('mario.png')
  5. img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
  6. template = cv.imread('mario_coin.png',0)
  7. w, h = template.shape[::-1]
  8. res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)
  9. threshold = 0.8
  10. loc = np.where( res >= threshold)
  11. for pt in zip(*loc[::-1]):
  12. cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
  13. cv.imwrite('res.png',img_rgb)

image.png

Hough直线

霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它。及时要检测的形状存在一点破坏或者扭曲也可以使用。
image.png

cv2.HoughLines() 返回值就是(ρ, θ)。ρ 的单位是像素,θ 的单位是弧度。这个函数的第一个参数是一个二值化图像,所以在进行霍夫变换之前要首先进行二值化,或者进行 Canny 边缘检测。

  1. import cv2 as cv
  2. import numpy as np
  3. img = cv.imread(cv.samples.findFile('sudoku.png'))
  4. gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  5. edges = cv.Canny(gray,50,150,apertureSize = 3)
  6. lines = cv.HoughLines(edges,1,np.pi/180,200)
  7. for line in lines:
  8. rho,theta = line[0]
  9. a = np.cos(theta)
  10. b = np.sin(theta)
  11. x0 = a*rho
  12. y0 = b*rho
  13. x1 = int(x0 + 1000*(-b))
  14. y1 = int(y0 + 1000*(a))
  15. x2 = int(x0 - 1000*(-b))
  16. y2 = int(y0 - 1000*(a))
  17. cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)
  18. cv.imwrite('houghlines3.jpg',img)

image.png

Probabilistic Hough Transform

不会对每一个点都进行计算,而是从一幅图像中随机选取(是不是也可以使用图像金字塔呢?)一个点集进行计算,对于直线检测来说这已经足够了。但是使用这种变换我们必须要降低阈值。image.png

**image.png

分水岭算法图像分割

任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水位的升高,不同山谷的水就会相遇汇合。为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的构建堤坝,直到所有的山峰都被水淹没。我们构建好的堤坝就是对图像的分割。这就是分水岭算法背后的哲理。
image.png

前景提取

开始时用户需要用一个矩形将前景区域框住(前景区域应该完全被包括在矩形框内部)。然后通过迭代分割得到最优结果。
image.png