OpenCV之Python学习笔记

图片 19

对图像处理经常用到DCT, Python下有很多带有DCT算法包,
这里使用OpenCV的DCT做变换, 并简单置0部分数据, 再查看反变换图像的效果.

OpenCV之Python学习笔记

直都在用Python+OpenCV做一些算法的原型。本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段。现在看
到一本国外的新书《OpenCV Computer Vision with
Python》,于是就看一遍,顺便把自己掌握的东西整合一下,写成学习笔记了。更需要的朋友参考。

阅读须知:

       
本文不是纯粹的译文,只是比较贴近原文的笔记;
        请设法购买到出版社出版的书,支持正版。

      
从书名就能看出来本书是介绍在Python中使用OpenCV,全书分为5章,两个附录:

  • 第一章OpenCV设置,介绍如何在Windows、Mac和Ubuntu上设置Pyhton、OpenCV和相关库的环境。还讨论了OpenCV社区、OpenCV文档以及官方的示例代码。
  • 第二章处理文件、摄像头和GUI,讨论OpenCV的I/O功能,接着使用面向对象的设计编写一个主应用程序,用于显示摄像头实时场景、处理键盘输入、将摄像头写入视频文件和静态图像文件。
  • 第三章图像过滤,介绍使用OpenCV、NumPy和SciPy来编写图像过滤器。过滤器可用于线性颜色操作、曲线颜色操作、模糊化、锐化和寻找边缘。本章修改第一章的主程序,将过滤器应用到实时摄像头场景中。
  • 第四章使用Haar
    Cascades追踪人脸
    ,本章将编写一个层次化的人脸追踪器,使用OpenCV定位图像中的脸部、眼睛、鼻子和嘴巴。同时还编写了用于复制和改变图像中某块区域的大小。同样,本章也将修改之前的主应用程序,让其可以用于找到并处理摄像头场景中的人脸。
  • 第五章检测前景/背景区域和深度。通过本章将了解有关OpenCV(在OpenNI和SensorKinect的支持下)从深度摄像头中获得的数据类型的信息。接着编写一些函数,使用这些数据对前景区域施加一些限制效果。最后将这些函数整合到主程序中,使得在处理人脸之前先进行细化操作。
  • 附录A,与Pygame整合。修改主程序,用Pygame替换OpenCV来处理特定的I/O事件。(Pygame提供了更多样的事件处理函数。)
  • 附录B,为自定义目标生成Haar
    Cascades
    ,允许我们检测一系列的OpenCV工具,来对任何类型的目标或模式构建跟踪器,而不仅仅是人脸。 

 

import numpy as np
import cv2
# from matplotlib import pyplot as plt

OpenCV Python教程(1、图像的载入、显示和保存)

本文是OpenCV  2 Computer Vision Application Programming
Cookbook读书笔记的第一篇。在笔记中将以Python语言改写每章的代码。

PythonOpenCV的配置这里就不介绍了。

注意,现在OpenCV for
Python就是通过NumPy进行绑定的。所以在使用时必须掌握一些NumPy的相关知识!

图像就是一个矩阵,在OpenCV for Python中,图像就是NumPy中的数组!

如果读取图像首先要导入OpenCV包,方法为:

[python] view
plain
copy

  1. import cv2  

y = cv2.imread(‘window.bmp’, 0)
# print(y.shape)
cv2.imshow(“gray”,y)
y1 = y.astype(np.float32)
# print(y1.dtype)
Y = cv2.dct(y1)
print(Y.shape)
for i in range(0,240):
     for j in range(0,320):
         if i > 100 or j > 100:
             Y[i,j] = 0
cv2.imshow(“Dct”,Y)
y2 = cv2.idct(Y)
# print(y2.dtype)
cv2.imshow(“iDCT”,y2.astype(np.uint8))
cv2.waitKey(0)

读取并显示图像

在Python中不需要声明变量,所以也就不需要C++中的cv::Mat
xxxxx了。只需这样:

[python] view
plain
copy

  1. img = cv2.imread(“D:\cat.jpg”)  

OpenCV目前支持读取bmp、jpg、png、tiff等常用格式。更详细的请参考OpenCV的参考文档。

接着创建一个窗口

[python] view
plain
copy

  1. cv2.namedWindow(“Image”)  

然后在窗口中显示图像

[python] view
plain
copy

  1. cv2.imshow(“Image”, img)  

最后还要添上一句:

[python] view
plain
copy

  1. cv2.waitKey (0)  

如果不添最后一句,在IDLE中执行窗口直接无响应。在命令行中执行的话,则是一闪而过。

完整的程序为:

[python] view
plain
copy

  1. import cv2   
  2.   
  3. img = cv2.imread(“D:\\cat.jpg”)   
  4. cv2.namedWindow(“Image”)   
  5. cv2.imshow(“Image”, img)   
  6. cv2.waitKey (0)  
  7. cv2.destroyAllWindows()  

最后释放窗口是个好习惯!

对于320×240的图像, 在频域对大于100的行和列都置零(相当于低通滤波)后,
反变换后图像有点模糊, 但还能还原大致效果.

创建/复制图像

新的OpenCV的接口中没有CreateImage接口。即没有cv2.CreateImage这样的函数。如果要创建图像,需要使用numpy的函数(现在使用OpenCV-Python绑定,numpy是必装的)。如下:

[python] view
plain
copy

  1. emptyImage = np.zeros(img.shape, np.uint8)  

在新的OpenCV-Python绑定中,图像使用NumPy数组的属性来表示图像的尺寸和通道信息。如果输出img.shape,将得到(500,
375, 3),这里是以OpenCV自带的cat.jpg为示例。最后的3表示这是一个RGB图像。

也可以复制原有的图像来获得一副新图像。

[python] view
plain
copy

  1. emptyImage2 = img.copy();  

如果不怕麻烦,还可以用cvtColor获得原图像的副本。

[python] view
plain
copy

  1. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  2. #emptyImage3[…]=0  

后面的emptyImage3[…]=0是将其转成空白的黑色图像。

图片 1

保存图像

保存图像很简单,直接用cv2.imwrite即可。

cv2.imwrite(“D:\\cat2.jpg”, img)

第一个参数是保存的路径及文件名,第二个是图像矩阵。其中,imwrite()有个可选的第三个参数,如下:

cv2.imwrite(“D:\\cat2.jpg”, img,[int(cv2.IMWRITE_JPEG_QUALITY),
5])

第三个参数针对特定的格式:
对于JPEG,其表示的是图像的质量,用0-100的整数表示,默认为95。
注意,cv2.IMWRITE_JPEG_QUALITY类型为Long,必须转换成int。下面是以不同质量存储的两幅图:图片 2

对于PNG,第三个参数表示的是压缩级别。cv2.IMWRITE_PNG_COMPRESSION,从0到9,压缩级别越高,图像尺寸越小。默认级别为3:

[python] view
plain
copy

  1. cv2.imwrite(“./cat.png”, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])   
  2. cv2.imwrite(“./cat2.png”, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  

保存的图像尺寸如下:图片 3

还有一种支持的图像,一般不常用。

完整的代码为:

[python] view
plain
copy

  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread(“./cat.jpg”)  
  5. emptyImage = np.zeros(img.shape, np.uint8)  
  6.   
  7. emptyImage2 = img.copy()  
  8.   
  9. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  10. #emptyImage3[…]=0  
  11.   
  12. cv2.imshow(“EmptyImage”, emptyImage)  
  13. cv2.imshow(“Image”, img)  
  14. cv2.imshow(“EmptyImage2”, emptyImage2)  
  15. cv2.imshow(“EmptyImage3”, emptyImage3)  
  16. cv2.imwrite(“./cat2.jpg”, img, [int(cv2.IMWRITE_JPEG_QUALITY), 5])  
  17. cv2.imwrite(“./cat3.jpg”, img, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
  18. cv2.imwrite(“./cat.png”, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])  
  19. cv2.imwrite(“./cat2.png”, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  
  20. cv2.waitKey (0)  
  21. cv2.destroyAllWindows()  

参考资料:
《OpenCV References Manuel》
《OpenCV  2 Computer Vision Application Programming Cookbook》
《OpenCV Computer Vision with Python》

 

cnblogs Tags: python,
opencv,
DCT

OpenCV Python教程(2、图像元素的访问、通道分离与合并)

访问像素

像素的访问和访问numpy中ndarray的方法完全一样,灰度图为:

[python] view
plain
copy

  1. img[j,i] = 255  

其中j,i分别表示图像的行和列。对于BGR图像,为:

[python] view
plain
copy

  1. img[j,i,0]= 255  
  2. img[j,i,1]= 255  
  3. img[j,i,2]= 255  

第三个数表示通道。

下面通过对图像添加人工的椒盐现象来进一步说明OpenCV
Python中需要注意的一些问题。完整代码如下:

[python] view
plain
copy

  1. import cv2  
  2. import numpy as np  
  3.   
  4. def salt(img, n):  
  5.     for k in range(n):  
  6.         i = int(np.random.random() * img.shape[1]);  
  7.         j = int(np.random.random() * img.shape[0]);  
  8.         if img.ndim == 2:   
  9.             img[j,i] = 255  
  10.         elif img.ndim == 3:   
  11.             img[j,i,0]= 255  
  12.             img[j,i,1]= 255  
  13.             img[j,i,2]= 255  
  14.     return img  
  15.   
  16. if __name__ == ‘__main__’:  
  17.     img = cv2.imread(“图像路径”)  
  18.     saltImage = salt(img, 500)  
  19.     cv2.imshow(“Salt”, saltImage)  
  20.     cv2.waitKey(0)  
  21.     cv2.destroyAllWindows()  

处理后能得到类似下面这样带有模拟椒盐现象的图片:

图片 4

上面的代码需要注意几点:

1、与C++不同,在Python中灰度图的img.ndim =
2,而C++中灰度图图像的通道数img.channel() =1

2、为什么使用np.random.random()?
这里使用了numpy的随机数,Python自身也有一个随机数生成函数。这里只是一种习惯,np.random模块中拥有更多的方法,而Python自
带的random只是一个轻量级的模块。不过需要注意的是np.random.seed()不是线程安全的,而Python自带的
random.seed()是线程安全的。如果使用随机数时需要用到多线程,建议使用Python自带的random()和random.seed(),
或者构建一个本地的np.random.Random类的实例。

分离、合并通道

由于OpenCV
Python和NumPy结合的很紧,所以即可以使用OpenCV自带的split函数,也可以直接操作numpy数组来分离通道。直接法为:

[python] view
plain
copy

  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread(“D:/cat.jpg”)  
  5. b, g, r = cv2.split(img)  
  6. cv2.imshow(“Blue”, r)  
  7. cv2.imshow(“Red”, g)  
  8. cv2.imshow(“Green”, b)  
  9. cv2.waitKey(0)  
  10. cv2.destroyAllWindows()  

其中split返回RGB三个通道,如果只想返回其中一个通道,可以这样:

[python] view
plain
copy

  1. b = cv2.split(img)[0]  
  2. g = cv2.split(img)[1]  
  3. r = cv2.split(img)[2]  

最后的索引指出所需要的通道。

也可以直接操作NumPy数组来达到这一目的:

[python] view
plain
copy

  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread(“D:/cat.jpg”)  
  5.   
  6. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  8. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  9.   
  10. b[:,:] = img[:,:,0]  
  11. g[:,:] = img[:,:,1]  
  12. r[:,:] = img[:,:,2]  
  13.   
  14. cv2.imshow(“Blue”, r)  
  15. cv2.imshow(“Red”, g)  
  16. cv2.imshow(“Green”, b)  
  17. cv2.waitKey(0)  
  18. cv2.destroyAllWindows()  

注意先要开辟一个相同大小的图片出来。这是由于numpy中数组的复制有些需要注意的地方,具体事例如下:

[python] view
plain
copy

  1. >>> c= np.zeros(img.shape, dtype=img.dtype)  
  2. >>> c[:,:,:] = img[:,:,:]  
  3. >>> d[:,:,:] = img[:,:,:]  
  4. >>> c is a  
  5. False  
  6. >>> d is a  
  7. False  
  8. >>> c.base is a  
  9. False  
  10. >>> d.base is a #注意这里!!!  
  11. True  

这里,d只是a的镜像,具体请参考《NumPy简明教程(二,数组3)》中的“复制和镜像”一节。

通道合并

同样,通道合并也有两种方法。第一种是OpenCV自带的merge函数,如下:

[python] view
plain
copy

  1. merged = cv2.merge([b,g,r]) #前面分离出来的三个通道  

接着是NumPy的方法:

[python] view
plain
copy

  1. mergedByNp = np.dstack([b,g,r])   

注意:这里只是演示,实际使用时请用OpenCV自带的merge函数!用NumPy组合的结果不能在OpenCV中其他函数使用,因为其组合方式与OpenCV自带的不一样,如下:

[python] view
plain
copy

  1. merged = cv2.merge([b,g,r])  
  2. print “Merge by OpenCV”   
  3. print merged.strides  
  4.   
  5. mergedByNp = np.dstack([b,g,r])   
  6. print “Merge by NumPy ”   
  7. print mergedByNp.strides  

结果为:

[python] view
plain
copy

  1. Merge by OpenCV  
  2. (1125, 3, 1)  
  3. Merge by NumPy  
  4. (1, 500, 187500)  

NumPy数组的strides属性表示的是在每个维数上以字节计算的步长。这怎么理解呢,看下面这个简单点的例子:

[python] view
plain
copy

  1. >>> a = np.arange(6)  
  2. >>> a  
  3. array([0, 1, 2, 3, 4, 5])  
  4. >>> a.strides  
  5. (4,)  

a数组中每个元素都是NumPy中的整数类型,占4个字节,所以第一维中相邻元素之间的步长为4(个字节)。

同样,2维数组如下:

[python] view
plain
copy

  1. >>> b = np.arange(12).reshape(3,4)  
  2. >>> b  
  3. array([[ 0,  1,  2,  3],  
  4.        [ 4,  5,  6,  7],  
  5.        [ 8,  9, 10, 11]])  
  6. >>> b.strides  
  7. (16, 4)  

从里面开始看,里面是一个4个元素的一维整数数组,所以步长应该为4。外面是一个含有3个元素,每个元素的长度是4×4=16。所以步长为16。

下面来看下3维数组:

[python] view
plain
copy

  1. >>> c = np.arange(27).reshape(3,3,3)  

其结果为:

[python] view
plain
copy

  1. array([[[ 0,  1,  2],  
  2.         [ 3,  4,  5],  
  3.         [ 6,  7,  8]],  
  4.   
  5.        [[ 9, 10, 11],  
  6.         [12, 13, 14],  
  7.         [15, 16, 17]],  
  8.   
  9.        [[18, 19, 20],  
  10.         [21, 22, 23],  
  11.         [24, 25, 26]]])  

根据前面了解的,推断下这个数组的步长。从里面开始算,应该为(3×4×3,3×4,4)。验证一下:

[python] view
plain
copy

  1. >>> c.strides  
  2. (36, 12, 4)  

完整的代码为:

[python] view
plain
copy

  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread(“D:/cat.jpg”)  
  5.   
  6. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  8. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  9.   
  10. b[:,:] = img[:,:,0]  
  11. g[:,:] = img[:,:,1]  
  12. r[:,:] = img[:,:,2]  
  13.   
  14. merged = cv2.merge([b,g,r])  
  15. print “Merge by OpenCV”   
  16. print merged.strides  
  17. print merged  
  18.   
  19. mergedByNp = np.dstack([b,g,r])   
  20. print “Merge by NumPy ”   
  21. print mergedByNp.strides  
  22. print mergedByNp  
  23.   
  24. cv2.imshow(“Merged”, merged)  
  25. cv2.imshow(“MergedByNp”, merged)  
  26. cv2.imshow(“Blue”, b)  
  27. cv2.imshow(“Red”, r)  
  28. cv2.imshow(“Green”, g)  
  29. cv2.waitKey(0)  
  30. cv2.destroyAllWindows() 

 

OpenCV Python教程(3、直方图的计算与显示)

本篇文章介绍如何用OpenCV
Python来计算直方图,并简略介绍用NumPy和Matplotlib计算和绘制直方图

直方图的背景知识、用途什么的就直接略过去了。这里直接介绍方法。

计算并显示直方图

与C++中一样,在Python中调用的OpenCV直方图计算函数为cv2.calcHist。

cv2.calcHist的原型为:

[python] view
plain
copy

  1. cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) #返回hist  

通过一个例子来了解其中的各个参数:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np  
  4.   
  5. image = cv2.imread(“D:/histTest.jpg”, 0)  
  6. hist = cv2.calcHist([image],  
  7.     [0], #使用的通道  
  8.     None, #没有使用mask  
  9.     [256], #HistSize  
  10.     [0.0,255.0]) #直方图柱的范围  

其中第一个参数必须用方括号括起来。

第二个参数是用于计算直方图的通道,这里使用灰度图计算直方图,所以就直接使用第一个通道;

第三个参数是Mask,这里没有使用,所以用None。

第四个参数是histSize,表示这个直方图分成多少份(即多少个直方柱)。第二个例子将绘出直方图,到时候会清楚一点。

第五个参数是表示直方图中各个像素的值,[0.0,
256.0]表示直方图能表示像素值从0.0到256的像素。

最后是两个可选参数,由于直方图作为函数结果返回了,所以第六个hist就没有意义了(待确定)

最后一个accumulate是一个布尔值,用来表示直方图是否叠加。

彩色图像不同通道的直方图

彩色图像不同通道的直方图

下面来看下彩色图像的直方图处理。以最著名的lena.jpg为例,首先读取并分离各通道:

[python] view
plain
copy

  1. import cv2      
  2. import numpy as np      
  3.       
  4. img = cv2.imread(“D:/lena.jpg”)      
  5. b, g, r = cv2.split(img)   

接着计算每个通道的直方图,这里将其封装成一个函数:

[python] view
plain
copy

  1. def calcAndDrawHist(image, color):    
  2.     hist= cv2.calcHist([image], [0], None, [256], [0.0,255.0])    
  3.     minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)    
  4.     histImg = np.zeros([256,256,3], np.uint8)    
  5.     hpt = int(0.9* 256);    
  6.         
  7.     for h in range(256):    
  8.         intensity = int(hist[h]*hpt/maxVal)    
  9.         cv2.line(histImg,(h,256), (h,256-intensity), color)    
  10.             
  11.     return histImg;   

这里只是之前代码的简单封装,所以注释就省掉了。

接着在主函数中使用:

[python] view
plain
copy

  1. if __name__ == ‘__main__’:    
  2.     img = cv2.imread(“D:/lena.jpg”)    
  3.     b, g, r = cv2.split(img)    
  4.     
  5.     histImgB = calcAndDrawHist(b, [255, 0, 0])    
  6.     histImgG = calcAndDrawHist(g, [0, 255, 0])    
  7.     histImgR = calcAndDrawHist(r, [0, 0, 255])    
  8.         
  9.     cv2.imshow(“histImgB”, histImgB)    
  10.     cv2.imshow(“histImgG”, histImgG)    
  11.     cv2.imshow(“histImgR”, histImgR)    
  12.     cv2.imshow(“Img”, img)    
  13.     cv2.waitKey(0)    
  14.     cv2.destroyAllWindows()   

这样就能得到三个通道的直方图了,如下:图片 5

更进一步

这样做有点繁琐,参考abid
rahman
的做法,无需分离通道,用折线来描绘直方图的边界可在一副图中同时绘制三个通道的直方图。方法如下:

[python] view
plain
copy

  1. #coding=utf-8    
  2. import cv2    
  3. import numpy as np    
  4.          
  5. img = cv2.imread(‘D:/lena.jpg’)    
  6. h = np.zeros((256,256,3)) #创建用于绘制直方图的全0图像    
  7.          
  8. bins = np.arange(256).reshape(256,1) #直方图中各bin的顶点位置    
  9. color = [ (255,0,0),(0,255,0),(0,0,255) ] #BGR三种颜色    
  10. for ch, col in enumerate(color):    
  11.     originHist = cv2.calcHist([img],[ch],None,[256],[0,256])    
  12.     cv2.normalize(originHist, originHist,0,255*0.9,cv2.NORM_MINMAX)    
  13.     hist=np.int32(np.around(originHist))    
  14.     pts = np.column_stack((bins,hist))    
  15.     cv2.polylines(h,[pts],False,col)    
  16.          
  17. h=np.flipud(h)    
  18.          
  19. cv2.imshow(‘colorhist’,h)    
  20. cv2.waitKey(0)    

结果如下图所示:

图片 6

代码说明:

这里的for循环是对三个通道遍历一次,每次绘制相应通道的直方图的折线。for循环的第一行是计算对应通道的直方图,经过上面的介绍,应该很容易就能明白。

这里所不同的是没有手动的计算直方图的最大值再乘以一个系数,而是直接调用了OpenCV的归一化函数。该函数将直方图的范围限定在
0-255×0.9之间,与之前的一样。下面的hist=
np.int32(np.around(originHist))先将生成的原始直方图中的每个元素四舍六入五凑偶取整(cv2.calcHist函数得
到的是float32类型的数组),接着将整数部分转成np.int32类型。即61.123先转成61.0,再转成61。注意,这里必须使用
np.int32(…)进行转换,numpy的转换函数可以对数组中的每个元素都进行转换,而Python的int(…)只能转换一个元素,如果
使用int(…),将导致only length-1 arrays can be converted to Python
scalars错误。

下面的pts =
np.column_stack((bins,hist))是将直方图中每个bin的值转成相应的坐标。比如hist[0]
=3,…,hist[126] = 178,…,hist[255] =
5;而bins的值为[[0],[1],[2]…,[255]]。使用np.column_stack将其组合成[0,
3]、[126, 178]、[255, 5]这样的坐标作为元素组成的数组。

最后使用cv2.polylines函数根据这些点绘制出折线,第三个False参数指出这个折线不需要闭合。第四个参数指定了折线的颜色。

当所有完成后,别忘了用h =
np.flipud(h)反转绘制好的直方图,因为绘制时,[0,0]在图像的左上角。这在直方图可视化一节中有说明。

NumPy版的直方图计算

在查阅abid
rahman
的资料时,发现他用NumPy的直方图计算函数“np.histogram也实现了相同的效果。如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np  
  4.   
  5. img = cv2.imread(‘D:/lena.jpg’)  
  6. h = np.zeros((300,256,3))  
  7. bins = np.arange(257)  
  8. bin = bins[0:-1]  
  9. color = [ (255,0,0),(0,255,0),(0,0,255) ]  
  10.   
  11. for ch,col in enumerate(color):  
  12.     item = img[:,:,ch]  
  13.     N,bins = np.histogram(item,bins)  
  14.     v=N.max()  
  15.     N = np.int32(np.around((N*255)/v))  
  16.     N=N.reshape(256,1)  
  17.     pts = np.column_stack((bin,N))  
  18.     cv2.polylines(h,[pts],False,col)  
  19.   
  20. h=np.flipud(h)  
  21.   
  22. cv2.imshow(‘img’,h)  
  23. cv2.waitKey(0)  

效果图和上面的一个相同。NumPy的histogram函数将在NumPy通用函数这篇博文中介绍,这里就不详细解释了。这里采用的是与一开始相同的比例系数的方法,参考本文的第二节。

另外,通过NumPy和matplotlib可以更方便的绘制出直方图,下面的代码供大家参考,如果有机会,再写的专门介绍matplotlib的文章。

[python] view
plain
copy

  1. import matplotlib.pyplot as plt  
  2. import numpy as np  
  3. import cv2  
  4.   
  5. img = cv2.imread(‘D:/lena.jpg’)  
  6. bins = np.arange(257)  
  7.   
  8. item = img[:,:,1]  
  9. hist,bins = np.histogram(item,bins)  
  10. width = 0.7*(bins[1]-bins[0])  
  11. center = (bins[:-1]+bins[1:])/2  
  12. plt.bar(center, hist, align = ‘center’, width = width)  
  13. plt.show()  

图片 7

这里显示的是绿色通道的直方图。

未完待续。。。如有错误请指正,本人会虚心接受并改正!谢谢!

 

OpenCV-Python教程(4、形态学处理)

  • 本文介绍使用OpenCV-Python进行形态学处理
  • 本文不介绍形态学处理的基本概念,所以读者需要预先对其有一定的了解。

定义结构元素

形态学处理的核心就是定义结构元素,在OpenCV-Python中,可以使用其自带的getStructuringElement函数,也可以直
接使用NumPy的ndarray来定义一个结构元素。首先来看用getStructuringElement函数定义一个结构元素:

[python] view
plain
copy

  1. element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))  

这就定义了一个5×5的十字形结构元素,如下:

图片 8

也可以用NumPy来定义结构元素,如下:

[python] view
plain
copy

  1. NpKernel = np.uint8(np.zeros((5,5)))  
  2. for i in range(5):  
  3.     NpKernel[2, i] = 1 #感谢chenpingjun1990的提醒,现在是正确的  
  4.     NpKernel[i, 2] = 1  

这两者方式定义的结构元素完全一样:

[python] view
plain
copy

  1. [[0 0 1 0 0]  
  2.  [0 0 1 0 0]  
  3.  [1 1 1 1 1]  
  4.  [0 0 1 0 0]  
  5.  [0 0 1 0 0]]  

 

这里可以看出,用OpenCV-Python内置的常量定义椭圆(MORPH_ELLIPSE)和十字形结构(MORPH_CROSS)元素要简单一些,如果定义矩形(MORPH_RECT)和自定义结构元素,则两者差不多。

本篇文章将用参考资料1中的相关章节的图片做测试:

图片 9

腐蚀和膨胀

下面先以腐蚀图像为例子介绍如何使用结构元素:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np  
  4.    
  5. img = cv2.imread(‘D:/binary.bmp’,0)  
  6. #OpenCV定义的结构元素  
  7. kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))  
  8.   
  9. #腐蚀图像  
  10. eroded = cv2.erode(img,kernel)  
  11. #显示腐蚀后的图像  
  12. cv2.imshow(“Eroded Image”,eroded);  
  13.   
  14. #膨胀图像  
  15. dilated = cv2.dilate(img,kernel)  
  16. #显示膨胀后的图像  
  17. cv2.imshow(“Dilated Image”,dilated);  
  18. #原图像  
  19. cv2.imshow(“Origin”, img)  
  20.   
  21. #NumPy定义的结构元素  
  22. NpKernel = np.uint8(np.ones((3,3)))  
  23. Nperoded = cv2.erode(img,NpKernel)  
  24. #显示腐蚀后的图像  
  25. cv2.imshow(“Eroded by NumPy kernel”,Nperoded);  
  26.   
  27. cv2.waitKey(0)  
  28. cv2.destroyAllWindows()  

如上所示,腐蚀和膨胀的处理很简单,只需设置好结构元素,然后分别调用cv2.erode(…)和cv2.dilate(…)函数即可,其中第一个参数是需要处理的图像,第二个是结构元素。返回处理好的图像。

结果如下:

图片 10

开运算和闭运算

了解形态学基本处理的同学都知道,开运算和闭运算就是将腐蚀和膨胀按照一定的次序进行处理。但这两者并不是可逆的,即先开后闭并不能得到原先的图像。代码示例如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np  
  4.    
  5. img = cv2.imread(‘D:/binary.bmp’,0)  
  6. #定义结构元素  
  7. kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))  
  8.   
  9. #闭运算  
  10. closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  
  11. #显示腐蚀后的图像  
  12. cv2.imshow(“Close”,closed);  
  13.   
  14. #开运算  
  15. opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  
  16. #显示腐蚀后的图像  
  17. cv2.imshow(“Open”, opened);  
  18.   
  19. cv2.waitKey(0)  
  20. cv2.destroyAllWindows()  

 

闭运算用来连接被误分为许多小块的对象,而开运算用于移除由图像噪音形成的斑点。因此,某些情况下可以连续运用这两种运算。如对一副二值图连续使用
闭运算和开运算,将获得图像中的主要对象。同样,如果想消除图像中的噪声(即图像中的“小点”),也可以对图像先用开运算后用闭运算,不过这样也会消除一
些破碎的对象。

对原始图像进行开运算和闭运算的结果如下:

图片 11

用形态学运算检测边和角点

这里通过一个较复杂的例子介绍如何用形态学算子检测图像中的边缘和拐角(这里只是作为介绍形态学处理例子,实际使用时请用Canny或Harris等算法)。

检测边缘

形态学检测边缘的原理很简单,在膨胀时,图像中的物体会想周围“扩张”;腐蚀时,图像中的物体会“收缩”。比较这两幅图像,由于其变化的区域只发生在边缘。所以这时将两幅图像相减,得到的就是图像中物体的边缘。这里用的依然是参考资料1中相关章节的图片:图片 12

代码如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy  
  4.   
  5. image = cv2.imread(“D:/building.jpg”,0);  
  6. #构造一个3×3的结构元素   
  7. element = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))  
  8. dilate = cv2.dilate(image, element)  
  9. erode = cv2.erode(image, element)  
  10.   
  11. #将两幅图像相减获得边,第一个参数是膨胀后的图像,第二个参数是腐蚀后的图像  
  12. result = cv2.absdiff(dilate,erode);  
  13.   
  14. #上面得到的结果是灰度图,将其二值化以便更清楚的观察结果  
  15. retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY);   
  16. #反色,即对二值图每个像素取反  
  17. result = cv2.bitwise_not(result);   
  18. #显示图像  
  19. cv2.imshow(“result”,result);   
  20. cv2.waitKey(0)  
  21. cv2.destroyAllWindows()  

处理结果如下:

图片 13

检测拐角

与边缘检测不同,拐角的检测的过程稍稍有些复杂。但原理相同,所不同的是先用十字形的结构元素膨胀像素,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,导致只有在拐角处才会“收缩”,而直线边缘都未发生变化。

第二步是用X形膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。所以当两幅图像相减时,只保留了拐角处。示意图如下(示意图来自参考资料1):

图片 14

代码如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3.   
  4. image = cv2.imread(“D:/building.jpg”, 0)  
  5. origin = cv2.imread(“D:/building.jpg”)  
  6. #构造5×5的结构元素,分别为十字形、菱形、方形和X型  
  7. cross = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))  
  8. #菱形结构元素的定义稍麻烦一些  
  9. diamond = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))  
  10. diamond[0, 0] = 0  
  11. diamond[0, 1] = 0  
  12. diamond[1, 0] = 0  
  13. diamond[4, 4] = 0  
  14. diamond[4, 3] = 0  
  15. diamond[3, 4] = 0  
  16. diamond[4, 0] = 0  
  17. diamond[4, 1] = 0  
  18. diamond[3, 0] = 0  
  19. diamond[0, 3] = 0  
  20. diamond[0, 4] = 0  
  21. diamond[1, 4] = 0  
  22. square = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))  
  23. x = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))  
  24. #使用cross膨胀图像  
  25. result1 = cv2.dilate(image,cross)  
  26. #使用菱形腐蚀图像  
  27. result1 = cv2.erode(result1, diamond)  
  28.   
  29. #使用X膨胀原图像   
  30. result2 = cv2.dilate(image, x)  
  31. #使用方形腐蚀图像   
  32. result2 = cv2.erode(result2,square)  
  33.   
  34. #result = result1.copy()  
  35. #将两幅闭运算的图像相减获得角   
  36. result = cv2.absdiff(result2, result1)  
  37. #使用阈值获得二值图  
  38. retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY)  
  39.   
  40. #在原图上用半径为5的圆圈将点标出。  
  41. for j in range(result.size):  
  42.     y = j / result.shape[0]   
  43.     x = j % result.shape[0]   
  44.   
  45.     if result[x, y] == 255:  
  46.         cv2.circle(image, (y, x), 5, (255,0,0))  
  47.   
  48. cv2.imshow(“Result”, image)  
  49. cv2.waitKey(0)  
  50. cv2.destroyAllWindows()  

注意,由于封装的缘故,OpenCV中函数参数中使用的坐标系和NumPy的ndarray的坐标系是不同的,在46行可以看出来。抽空我向OpenCV邮件列表提一下,看我的理解是不是正确的。

大家可以验证一下,比如在代码中插入这两行代码,就能知道结果了:

[python] view
plain
copy

  1. cv2.circle(image, (5, 10), 5, (255,0,0))  
  2. image[5, 10] = 0  

通过上面的代码就能检测到图像中的拐角并标出来,效果图如下:

图片 15

当然,这只是个形态学处理示例,检测结果并不好。

未完待续…

在将来的某一篇文章中将做个总结,介绍下OpenCV中常用的函数,如threshold、bitwise_xxx,以及绘制函数等。

参考资料:

1、《Opencv2 Computer Vision Application Programming Cookbook》

2、《OpenCV References Manule》

如果觉得本文写的还可以的话,请轻点“顶”,方便读者、以及您的支持是我写下去的最大的两个动力。

 

 

OpenCV-Python教程(5、初级滤波内容)

简介

过滤是信号和图像处理中基本的任务。其目的是根据应用环境的不同,选择性的提取图像中某些认为是重要的信息。过滤可以移除图像中的噪音、提取感兴趣
的可视特征、允许图像重采样,等等。其源自于一般的信号和系统理论,这里将不介绍该理论的细节。但本章会介绍关于过滤的基本概念,以及如何在图像处理程序
中使用滤波器。首先,简要介绍下频率域分析的概念。

当我们观察一张图片时,我们观察的是图像中有多少灰度级(或颜色)及其分布。根据灰度分布的不同来区分不同的图像。但还有其他方面可以对图像进行分
析。我们可以观察图像中灰度的变化。某些图像中包含大量的强度不变的区域(如蓝天),而在其他图像中的灰度变化可能会非常快(如包含许多小物体的拥挤的图
像)。因此,观察图像中这些变化的频率就构成了另一条分类图像的方法。这个观点称为频域。而通过观察图像灰度分布来分类图像称为空间域。

频域分析将图像分成从低频到高频的不同部分。低频对应图像强度变化小的区域,而高频是图像强度变化非常大的区域。目前已存在若干转换方法,如傅立叶
变换或余弦变换,可以用来清晰的显示图像的频率内容。注意,由于图像是一个二维实体,所以其由水平频率(水平方向的变化)和竖直频率(竖直方向的变化)共
同组成。

在频率分析领域的框架中,滤波器是一个用来增强图像中某个波段或频率并阻塞(或降低)其他频率波段的操作。低通滤波器是消除图像中高频部分,但保留低频部分。高通滤波器消除低频部分

本篇文章介绍在OpenCV-Python中实现的初级的滤波操作,下一篇文章介绍更加复杂的滤波原理及其实现。

本篇文章使用传统的lena作为实验图像。

用低通滤波来平滑图像

低通滤波器的目标是降低图像的变化率。如将每个像素替换为该像素周围像素的均值。这样就可以平滑并替代那些强度变化明显的区域。在OpenCV中,可以通过blur函数做到这一点:

[python] view
plain
copy

  1. dst = cv2.blur(image,(5,5));   

其中dst是blur处理后返回的图像,参数一是输入的待处理图像,参数2是低通滤波器的大小。其后含有几个可选参数,用来设置滤波器的细节,具体可查阅参考资料2。不过这里,这样就够了。下面是一个简单的示例代码:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3.   
  4. img = cv2.imread(“D:/lena.jpg”, 0)  
  5. result = cv2.blur(img, (5,5))  
  6.   
  7. cv2.imshow(“Origin”, img)  
  8. cv2.imshow(“Blur”, result)  
  9.   
  10. cv2.waitKey(0)  
  11. cv2.destroyAllWindows()  

结果如下,左边是平滑过的图像,右边是原图像:

图片 16

这种滤波器又称为boxfilter(注,这与化学上的箱式过滤器是两码事,所以这里就不翻译了)。所以也可通过OpenCV的cv2.bofxfilter(…)函数来完成相同的工作。如下:

[python] view
plain
copy

  1. result1 = cv2.boxFilter(img, -1, (5, 5))  

这行代码与上面使用blur函数的效果完全相同。其中第二个参数的-1表示输出图像使用的深度与输入图像相同。后面还有几个可选参数,具体可查阅OpenCV文档。

高斯模糊

在某些情况下,需要对一个像素的周围的像素给予更多的重视。因此,可通过分配权重来重新计算这些周围点的值。这可通过高斯函数(钟形函数,即喇叭形数)的权重方案来解决。cv::GaussianBlur函数可作为滤波器用下面的方法调用:

[python] view
plain
copy

  1. gaussianResult = cv2.GaussianBlur(img,(5,5),1.5)  

区别

低通滤波与高斯滤波的不同之处在于:低通滤波中,滤波器中每个像素的权重是相同的,即滤波器是线性的。而高斯滤波器中像素的权重与其距中心像素的距离成比例。关于高斯模糊的详细内容,抽空将写一篇独立的文章介绍。

使用中值滤波消除噪点

前面介绍的是线性过滤器,这里介绍非线性过滤器——中值滤波器。由于中值滤波器对消除椒盐现象特别有用。所以我们使用第二篇教程中椒盐函数先对图像进行处理,将处理结果作为示例图片。

调用中值滤波器的方法与调用其他滤波器的方法类似,如下:

[python] view
plain
copy

  1. result = cv2.medianBlur(image,5)  

函数返回处理结果,第一个参数是待处理图像,第二个参数是孔径的尺寸,一个大于1的奇数。比如这里是5,中值滤波器就会使用5×5的范围来计算。即对像素的中心值及其5×5邻域组成了一个数值集,对其进行处理计算,当前像素被其中值替换掉。

如果在某个像素周围有白色或黑色的像素,这些白色或黑色的像素不会选择作为中值(最大或最小值不用),而是被替换为邻域值。代码如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np    
  4.     
  5. def salt(img, n):    
  6.     for k in range(n):    
  7.         i = int(np.random.random() * img.shape[1]);    
  8.         j = int(np.random.random() * img.shape[0]);    
  9.         if img.ndim == 2:     
  10.             img[j,i] = 255    
  11.         elif img.ndim == 3:     
  12.             img[j,i,0]= 255    
  13.             img[j,i,1]= 255    
  14.             img[j,i,2]= 255    
  15.     return img   
  16.   
  17. img = cv2.imread(“D:/lena.jpg”, 0)  
  18. result = salt(img, 500)  
  19. median = cv2.medianBlur(result, 5)  
  20.   
  21.   
  22. cv2.imshow(“Salt”, result)  
  23. cv2.imshow(“Median”, median)  
  24.   
  25. cv2.waitKey(0)  

处理结果如下:

图片 17

由于中值滤波不会处理最大和最小值,所以就不会受到噪声的影响。相反,如果直接采用blur进行均值滤波,则不会区分这些噪声点,滤波后的图像会受到噪声的影响。

中值滤波器在处理边缘也有优势。但中值滤波器会清除掉某些区域的纹理(如背景中的树)。

其他

由于方向滤波器与这里的原理有较大的出入,所以将用独立的一篇文章中介绍其原理以及实现。

参考资料:

1、《Opencv2 Computer Vision Application Programming Cookbook》

2、《OpenCV References Manule》

 

 

OpenCV-Python教程(6、Sobel算子)

Sobel算子

原型

Sobel算子依然是一种过滤器,只是其是带有方向的。在OpenCV-Python中,使用Sobel的算子的函数原型如下:

[python] view
plain
copy

  1. dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])  

函数返回其处理结果。

前四个是必须的参数:

  • 第一个参数是需要处理的图像;
  • 第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
  • dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。

其后是可选的参数:

  • dst不用解释了;
  • ksize是Sobel算子的大小,必须为1、3、5、7。
  • scale是缩放导数的比例常数,默认情况下没有伸缩系数;
  • delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
  • borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。

使用

在OpenCV-Python中,Sobel函数的使用如下:

[python] view
plain
copy

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np    
  4.   
  5. img = cv2.imread(“D:/lion.jpg”, 0)  
  6.   
  7. x = cv2.Sobel(img,cv2.CV_16S,1,0)  
  8. y = cv2.Sobel(img,cv2.CV_16S,0,1)  
  9.   
  10. absX = cv2.convertScaleAbs(x)   # 转回uint8  
  11. absY = cv2.convertScaleAbs(y)  
  12.   
  13. dst = cv2.addWeighted(absX,0.5,absY,0.5,0)  
  14.   
  15. cv2.imshow(“absX”, absX)  
  16. cv2.imshow(“absY”, absY)  
  17.   
  18. cv2.imshow(“Result”, dst)  
  19.   
  20. cv2.waitKey(0)  
  21. cv2.destroyAllWindows()   

解释

在Sobel函数的第二个参数这里使用了cv2.CV_16S。因为OpenCV文档中对Sobel算子的介绍中有这么一句:“in
the case of 8-bit input images it will result in truncated
derivatives”。即Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以Sobel建立的图
像位数不够,会有截断。因此要使用16位有符号的数据类型,即cv2.CV_16S。

在经过处理后,别忘了用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。convertScaleAbs()的原型为:

[python] view
plain
copy

  1. dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])  

其中可选参数alpha是伸缩系数,beta是加到结果上的一个值。结果返回uint8类型的图片。

由于Sobel算子是在两个方向计算的,最后还需要用cv2.addWeighted(…)函数将其组合起来。其函数原型为:

[python] view
plain
copy

  1. dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])  

其中alpha是第一幅图片中元素的权重,beta是第二个的权重,gamma是加到最后结果上的一个值。

结果

原图像为:

图片 18

结果为:

图片 19