您的位置:首页 > 编程语言 > Python开发

OpenCV Python教程(3)(4)(5): 直方图的计算与显示 形态学处理 初级滤波内

2016-05-05 20:52 776 查看

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

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

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


计算并显示直方图

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

cv2.calcHist的原型为:

[python] view
plain copy

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

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

[python] view
plain copy

#coding=utf-8

import cv2

import numpy as np

image = cv2.imread("D:/histTest.jpg", 0)

hist = cv2.calcHist([image],

[0], #使用的通道

None, #没有使用mask

[256], #HistSize

[0.0,255.0]) #直方图柱的范围

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

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

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

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

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

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

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

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


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

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

[python] view
plain copy

import cv2

import numpy as np

img = cv2.imread("D:/lena.jpg")

b, g, r = cv2.split(img)

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

[python] view
plain copy

def calcAndDrawHist(image, color):

hist= cv2.calcHist([image], [0], None, [256], [0.0,255.0])

minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)

histImg = np.zeros([256,256,3], np.uint8)

hpt = int(0.9* 256);

for h in range(256):

intensity = int(hist[h]*hpt/maxVal)

cv2.line(histImg,(h,256), (h,256-intensity), color)

return histImg;

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

接着在主函数中使用:

[python] view
plain copy

if __name__ == '__main__':

img = cv2.imread("D:/lena.jpg")

b, g, r = cv2.split(img)

histImgB = calcAndDrawHist(b, [255, 0, 0])

histImgG = calcAndDrawHist(g, [0, 255, 0])

histImgR = calcAndDrawHist(r, [0, 0, 255])

cv2.imshow("histImgB", histImgB)

cv2.imshow("histImgG", histImgG)

cv2.imshow("histImgR", histImgR)

cv2.imshow("Img", img)

cv2.waitKey(0)

cv2.destroyAllWindows()

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



更进一步

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

[python] view
plain copy

#coding=utf-8

import cv2

import numpy as np

img = cv2.imread('D:/lena.jpg')

h = np.zeros((256,256,3)) #创建用于绘制直方图的全0图像

bins = np.arange(256).reshape(256,1) #直方图中各bin的顶点位置

color = [ (255,0,0),(0,255,0),(0,0,255) ] #BGR三种颜色

for ch, col in enumerate(color):

originHist = cv2.calcHist(

代码说明:

这里的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

#coding=utf-8

import cv2

import numpy as np

img = cv2.imread('D:/lena.jpg')

h = np.zeros((300,256,3))

bins = np.arange(257)

bin = bins[0:-1]

color = [ (255,0,0),(0,255,0),(0,0,255) ]

for ch,col in enumerate(color):

item = img[:,:,ch]

N,bins = np.histogram(item,bins)

v=N.max()

N = np.int32(np.around((N*255)/v))

N=N.reshape(256,1)

pts = np.column_stack((bin,N))

cv2.polylines(h,[pts],False,col)

h=np.flipud(h)

cv2.imshow('img',h)

cv2.waitKey(0)

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

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

[python] view
plain copy

import matplotlib.pyplot as plt

import numpy as np

import cv2

img = cv2.imread('D:/lena.jpg')

bins = np.arange(257)

item = img[:,:,1]

hist,bins = np.histogram(item,bins)

width = 0.7*(bins[1]-bins[0])

center = (bins[:-1]+bins[1:])/2

plt.bar(center, hist, align = 'center', width = width)

plt.show()



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

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

提示:

转载请详细注明原作者及出处,谢谢!

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


定义结构元素

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

[python] view
plain copy

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

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



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

[python] view
plain copy

NpKernel = np.uint8(np.zeros((5,5)))

for i in range(5):

NpKernel[2, i] = 1 #感谢chenpingjun1990的提醒,现在是正确的

NpKernel[i, 2] = 1

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

[python] view
plain copy

[[0 0 1 0 0]

[0 0 1 0 0]

[1 1 1 1 1]

[0 0 1 0 0]

[0 0 1 0 0]]

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

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




腐蚀和膨胀

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

[python] view
plain copy

#coding=utf-8

import cv2

import numpy as np

img = cv2.imread('D:/binary.bmp',0)

#OpenCV定义的结构元素

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))

#腐蚀图像

eroded = cv2.erode(img,kernel)

#显示腐蚀后的图像

cv2.imshow("Eroded Image",eroded);

#膨胀图像

dilated = cv2.dilate(img,kernel)

#显示膨胀后的图像

cv2.imshow("Dilated Image",dilated);

#原图像

cv2.imshow("Origin", img)

#NumPy定义的结构元素

NpKernel = np.uint8(np.ones((3,3)))

Nperoded = cv2.erode(img,NpKernel)

#显示腐蚀后的图像

cv2.imshow("Eroded by NumPy kernel",Nperoded);

cv2.waitKey(0)

cv2.destroyAllWindows()

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

结果如下:




开运算和闭运算

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

[python] view
plain copy

#coding=utf-8

import cv2

import numpy as np

img = cv2.imread('D:/binary.bmp',0)

#定义结构元素

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))

#闭运算

closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

#显示腐蚀后的图像

cv2.imshow("Close",closed);

#开运算

opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

#显示腐蚀后的图像

cv2.imshow("Open", opened);

cv2.waitKey(0)

cv2.destroyAllWindows()

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

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




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

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


检测边缘

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


代码如下:

[python] view
plain copy

#coding=utf-8

import cv2

import numpy

image = cv2.imread("D:/building.jpg",0);

#构造一个3×3的结构元素

element = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))

dilate = cv2.dilate(image, element)

erode = cv2.erode(image, element)

#将两幅图像相减获得边,第一个参数是膨胀后的图像,第二个参数是腐蚀后的图像

result = cv2.absdiff(dilate,erode);

#上面得到的结果是灰度图,将其二值化以便更清楚的观察结果

retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY);

#反色,即对二值图每个像素取反

result = cv2.bitwise_not(result);

#显示图像

cv2.imshow("result",result);

cv2.waitKey(0)

cv2.destroyAllWindows()

处理结果如下:




检测拐角

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

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



代码如下:

[python] view
plain copy

#coding=utf-8

import cv2

image = cv2.imread("D:/building.jpg", 0)

origin = cv2.imread("D:/building.jpg")

#构造5×5的结构元素,分别为十字形、菱形、方形和X型

cross = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))

#菱形结构元素的定义稍麻烦一些

diamond = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))

diamond[0, 0] = 0

diamond[0, 1] = 0

diamond[1, 0] = 0

diamond[4, 4] = 0

diamond[4, 3] = 0

diamond[3, 4] = 0

diamond[4, 0] = 0

diamond[4, 1] = 0

diamond[3, 0] = 0

diamond[0, 3] = 0

diamond[0, 4] = 0

diamond[1, 4] = 0

square = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))

x = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))

#使用cross膨胀图像

result1 = cv2.dilate(image,cross)

#使用菱形腐蚀图像

result1 = cv2.erode(result1, diamond)

#使用X膨胀原图像

result2 = cv2.dilate(image, x)

#使用方形腐蚀图像

result2 = cv2.erode(result2,square)

#result = result1.copy()

#将两幅闭运算的图像相减获得角

result = cv2.absdiff(result2, result1)

#使用阈值获得二值图

retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY)

#在原图上用半径为5的圆圈将点标出。

for j in range(result.size):

y = j / result.shape[0]

x = j % result.shape[0]

if result[x, y] == 255:

cv2.circle(image, (y, x), 5, (255,0,0))

cv2.imshow("Result", image)

cv2.waitKey(0)

cv2.destroyAllWindows()

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

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

[python] view
plain copy

cv2.circle(image, (5, 10), 5, (255,0,0))

image[5, 10] = 0

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



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

未完待续...

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


参考资料:

1、《Opencv2 Computer Vision Application Programming Cookbook》

2、《OpenCV References Manule》

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



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

本篇文章介绍如何用OpenCV-Python来实现初级滤波功能。

提示:

转载请详细注明原作者及出处,谢谢!

本文介绍使用OpenCV-Python实现基本的滤波处理
本文不介绍滤波处理的详细概念,所以读者需要预先对其有一定的了解。


简介

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

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

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

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

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

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


用低通滤波来平滑图像

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

[python] view
plain copy

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

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

[python] view
plain copy

#coding=utf-8

import cv2

img = cv2.imread("D:/lena.jpg", 0)

result = cv2.blur(img, (5,5))

cv2.imshow("Origin", img)

cv2.imshow("Blur", result)

cv2.waitKey(0)

cv2.destroyAllWindows()

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



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

[python] view
plain copy

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

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


高斯模糊

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

[python] view
plain copy

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


区别

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


使用中值滤波消除噪点

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

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

[python] view
plain copy

result = cv2.medianBlur(image,5)

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

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

[python] view
plain copy

#coding=utf-8

import cv2

import numpy as np

def salt(img, n):

for k in range(n):

i = int(np.random.random() * img.shape[1]);

j = int(np.random.random() * img.shape[0]);

if img.ndim == 2:

img[j,i] = 255

elif img.ndim == 3:

img[j,i,0]= 255

img[j,i,1]= 255

img[j,i,2]= 255

return img

img = cv2.imread("D:/lena.jpg", 0)

result = salt(img, 500)

median = cv2.medianBlur(result, 5)

cv2.imshow("Salt", result)

cv2.imshow("Median", median)

cv2.waitKey(0)

处理结果如下:



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

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


其他

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


参考资料:

1、《Opencv2 Computer Vision Application Programming Cookbook》

2、《OpenCV References Manule》

from: http://blog.csdn.net/sunny2038/article/category/904451
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: