OpenCV是一套采用C/C++编写的开源跨平台计算机视觉库,它提供了两套Python调用接口。其一是cv2模块:针对OpenCV 2.x API创建的,它直接采用NumPy的数组对象表示图
其二是为了兼容OpenCV 1.x API,在cv模块下提供了原来的OpenCV 1.x API的扩展
cv(from cv2 import cv)。
"图像的输入(imread)输出(write)"
import cc2
img=cv2.imread("路径")#从指定的文件路径读入图像数据,它返回的是一个uint8的数组(512,512,3)
cv2.write("路径")#将图像数据写入文件
#的彩色图像通过cvtColor()转换成表示黑白图像的二维数组,所有的颜色转换常数均以COLOR_开头
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print img_gray.shape #(512,512)
注意:imread()函数读入彩色图像时,第2轴按照蓝、绿、红的顺序排列。这种顺序与matplotlib的imshow()函数所需的三通道的顺序正好相反。因此,若需要在matplotlib中显示图像,则需要反转第2轴:img[:, :, ::-1]。
图像处理中最基本的算法就是将图像和某个卷积核进行卷积,使用不同的卷积核可以得到各种不同的图像处理效果。OpenCV提供了filter2D()来完成图像的卷积运算,调用方式如下:
filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
参数列表:
src参数是原始图像
dst参数是目标图像,若省略dst参数,将创造一个新的数组来保存图像数据
ddepth参数用于指定目标图像的每个通道的数据类型,负数表示其数据类型和原始图像相同
kernel参数设置卷积核,它将与原始图像的每个通道进行卷积计算,并将结果存储到目标图像的对应通道中
anchor参数指定卷积核的锚点位置,当它为默认值(-1, -1)时,以卷积核的中心为锚点
delta参数指定在将计算结果存储到dst中之前对数值的偏移量。
filter2D()的卷积运算过程如下:
(1)对于图像src中的每个像素点,让它和卷积核的锚点对齐。
(2)对于图像src中与卷积核重叠的部分,计算像素值和卷积核的值乘积。
(3)图像dst中的像素点的值为上面所有乘积的总和。
显然当卷积核的尺寸很大时,上述方法的运算速度将会很慢。因此对于较大的卷积核,filter2D()将使用离散傅立叶变换相关的算法进行卷积运算。
import cv2
import numpy as np
import matplotlib.pyplot as pl
#import matplotlib as pl,pl.subplots失败
pl.rcParams["font.family"] = "SimHei"#直接修改配置字典,设置默认字体
src = cv2.imread("E:/ruanjianDM/jupyternoerbookDM/LYF.jpg")
kernels = [
(u"低通滤波器",np.array([[1, 1, 1],[1, 2, 1],[1, 1, 1]])*0.1),
(u"高通滤波器",np.array([[0.0,-1, 0],[-1,5,-1],[0,-1,0]])),
(u"边缘检测",np.array([[-1.0, -1, -1],[-1, 8, -1],[-1, -1, -1]]))
]#列表,元素为元胞
index = 0,
fig, axes = pl.subplots(1, 3, figsize=(12, 4.3))
for ax, (name, kernel) in zip(axes, kernels):#zip 将数组和元胞放在一起
print("ax:",ax,"\n","name",name,"\n","knl",kernel)
dst=cv2.filter2D(src, -1, kernel)
# 由于matplotlib的颜色顺序和OpenCV的顺序相反
ax.imshow(dst[:, :, ::-1])#因为ax是子图,是matplotlib中的,故需要换顺序
ax.set_title(name)
ax.axis("off")
fig.subplots_adjust(0.02, 0, 0.98, 1, 0.02, 0)#调节窗口大小
"""1.zip可以在处理循环时用到,返回一个将多个可迭代对象组合成一个元组序列的迭代器。
每个元组都包含所有可迭代对象中该位置的元素。"""
for i,p in zip(['a', 'b', 'c'], [1, 2, 3]):
print(i,p)
#a 1
#b 2
#c 3
"2.zip除了可以将两个列表组合到一起之外,还可以使用星号拆封列表,返回的是单个元组"
some_list = [('a', 1), ('b', 2), ('c', 3)]
letters, nums = zip(*some_list)
print(letters) # ('a', 'b', 'c')
print(nums) # (1, 2, 3)
"""3.enumerate 是一个会返回元组迭代器的内置函数,这些元组包含列表的索引和值。
当你需要在循环中获取可迭代对象的每个元素及其"索引"时,将经常用到该函数。
"""
letters = ['a', 'b', 'c']
for i, letter in enumerate(letters):
print(i, letter)
#0 a
#1 b
#2 c
"""zip和enumerate组合使用"""
for k, (key,value) in enumerate(zip(['a', 'b', 'c'], [1, 2, 3])):
result[k]={key:value}
#{'a':1, 'b':2, 'c':3}
有些特殊的卷积核可以表示成一个列矢量和一个行矢量的乘积,这时只需要将原始图像按顺序与这两个矢量进行卷积,所得到的最终结果和直接与卷积核进行卷积的结果相同。
由于将一个N×M的矩阵分解成了两个N×1和1×M的矩阵,因此对于较大的卷积核能大幅度地提高计算速度。
OpenCV提供了sepFilter2D()来进行这种分步卷积,调用参数如下:
sepFilter2D(src, ddepth, kernelX, kernelY[, dst[, anchor[, delta[, borderType]]]])
其中kernelX和kernelY分别为行卷积核和列卷积核。
import numpy as np
img = np.random.rand(1000,1000)
pl.imshow(img)
#行卷积核,列卷积核
row = cv2.getGaussianKernel(7, -1)
col = cv2.getGaussianKernel(5, -1)
#卷积核
kernel = np.dot(col[:], row[:].T)
%time img2 = cv2.filter2D(img, -1, kernel)
%time img3 = cv2.sepFilter2D(img, -1, row, col)
print ("error={}".format(np.max(np.abs(img2[:] - img3[:]))))
由于卷积计算很常用,因此OpenCV提供了一些高级函数来直接完成与某种特定卷积核的卷积计算。例如平均模糊blur()、高斯模糊GaussianBlur()、用于边缘检测的差分运算Sobel()和Laplacian()等。关于这些函数的用法请读者自行参考OpenCV的文档,这里就不再举例了。
形态学运算是针对二值图像依据数学形态学(Mathematical Morphology)的集合论方法发展起来的图像处理方法。
通常,形态学图像处理表现为一种邻域运算形式,一种特殊定义的领域称之为“结构元素”,在每个像素位置它与二值图像对应的区域进行特定的逻辑运算,逻辑运算的结果为输出图像的响应像素。
形态学处理的核心就是定义结构元素,在OpenCV-Python中,可以使用其自带的getStructuringElement函数,也可以直接使用NumPy的ndarray来定义一个结构元素。
(形象图如下:)
简单来讲,形态学操作就是基于形状的一系列图像处理操作,通过将结构元素作用于输入图像来产生输出图像。
最基本的形态学操作有:腐蚀与膨胀(Erosion和Dilation)。
广泛应用于:消除噪声、分割(isolate)独立的图像元素以及连接(join)相邻的元素和寻找图像中的明显的极大值区域或极小值区域。
膨胀:求局部最大值,把二值图像各1像素连接成分的边界扩大一层(填充边缘或0像素内部的孔)
腐蚀:求局部最小值,把二值图像各1像素连接成分的边界点去掉从而缩小一层(可提取骨干信息,去掉毛刺,去掉孤立的0像素)有一个1结果为1,故小白点周围是黑色,有一个白点可以去掉小白点,
开运算:先腐蚀再膨胀,可以去掉目标外的孤立点。
闭运算:先膨胀再腐蚀,可以去掉目标内的孔。
顶帽:原图像与“开运算“的结果图之差,用于背景提取。
黑帽:原图像与“闭运算“的结果图之差。
形态学梯度:实为膨胀图与腐蚀图之差。突出高亮区域的外围。
膨胀dilate(),腐蚀erode():两者参数相同
dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
src参数是原始图像;
kernel参数是结构元素,它指定针对哪些周围像素进行计算;
anchor参数指定锚点的位置,其默认值为结构元素的中心;
iterations参数指定处理次数
morphologyEx()使用膨胀和收缩实现一些更高级的形态学处理。
它比dilate()多了一个op参数,用于指定运算的类型。
将彩色图像转化为二值图像:必须灰度图像过渡,注意在opencv中黑色是0,白色是255
import cv2
import matplotlib.pyplot as pl
img = cv2.imread("E:/ruanjianDM/jupyternoerbookDM/LYF.jpg")
imgg=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#彩色图像转为灰度图
fig, axes = pl.subplots(1, 2, figsize=(7, 8))
#待处理的图像,阈值,最大值,参数
imge1,imge2=cv2.threshold(imgg,80,255,cv2.THRESH_BINARY_INV)
#pl.imshow(imgg)
axes[0].imshow(imgg,cmap='gray')#显示灰度图
axes[1].imshow(imge2,cmap='binary')#显示二值图
腐蚀,取局部极小值,故去掉了小黑点;膨胀取局部极大值,故去掉了小白点。
腐蚀、膨胀、开闭运算、形态学梯度代码如下
pl.rcParams["font.family"] = "SimHei"#直接修改配置字典,设置默认字体
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # 椭圆结构
#[[0, 1, 0],
# [1, 1, 1],
# [0, 1, 0]]
#kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)) # 十字结构
#kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 矩形结构
fig, axes = pl.subplots(2, 3, figsize=(11, 13))
a0,a1,a2,a3,a4,a5=axes[0,0],axes[0,1],axes[0,2],axes[1,0],axes[1,1],axes[1,2]
a0.set_title("二值原图")
a0.imshow(imge2,cmap='binary')
"腐蚀(去白小点)"
img1 = cv2.erode(imge2, kernel) # 腐蚀
a1.set_title("腐蚀去(小黑点)")
a1.imshow(img1,cmap='binary')
"膨胀(去黑小点)"
img2 = cv2.dilate(imge2, kernel) # 膨胀
a2.set_title("膨胀去(小白点)")
a2.imshow(img2,cmap='binary')
"先腐蚀后膨胀叫开运算(因为先腐蚀会分开物体,这样容易记住),其作用是:分离物体,消除小区域。"
opening = cv2.morphologyEx(imge2, cv2.MORPH_OPEN, kernel) # 开运算
a3.set_title("开运算--去除目标外的点")
a3.imshow(opening,cmap='binary')
"闭运算则相反:先膨胀后腐蚀(先膨胀会使白色的部分扩张,以至于消除闭合物体里面的小黑洞,所以叫闭运算)"
closing = cv2.morphologyEx(imge2, cv2.MORPH_CLOSE, kernel) # 闭运算
a4.set_title("闭运算--去除目标内的孔")
a4.imshow(closing,cmap='binary')
"形态学梯度:膨胀图减去腐蚀图,dilation - erosion,这样会得到物体的轮廓"
gradient = cv2.morphologyEx(imge2, cv2.MORPH_GRADIENT, kernel) # 形态学梯度
a5.set_title("物体的轮廓")
a5.imshow(gradient,cmap='binary')
pl.rcParams["font.family"] = "SimHei"#直接修改配置字典,设置默认字体
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # 椭圆结构
#kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)) # 十字结构
#kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 矩形结构
fig,axes = pl.subplots(1, 3, figsize=(11, 13))
a0,a1,a2=axes[0],axes[1],axes[2]
a0.set_title("二值原图")
a0.imshow(imge2,cmap='binary')
"顶帽:原图减去开运算后的图"
tophat = cv2.morphologyEx(imge2, cv2.MORPH_TOPHAT, kernel)
a1.set_title("顶帽目标外的点")
a1.imshow(tophat,cmap='binary')
"黑帽:闭运算减去原图的图"
blackhat = cv2.morphologyEx(imge2, cv2.MORPH_BLACKHAT, kernel)
a2.set_title("黑帽目标内的孔")
a2.imshow(blackhat,cmap='binary')
区域填充实际上就是条件膨胀。填充函数floodFill()在图像处理中经常用于标识或分离图像中的某些特定部分。
它的调用方式为:
floodFill(image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]])
image参数是需要填充的图像;
seedPoint参数为填充的起始点,我们称之为种子点;
newVal参数为填充所使用的颜色值;
loDiff和upDiff参数是填充的下限和上限容差;
flags参数是填充的算法标志。
填充从seedPoint指定的种子坐标开始,图像中与当前的填充区域颜色“相近”的点将被添加进填充区域,从而逐步扩大填充区域,直到没有新的点能添加进填充区域为止。
颜色相近的判断方法有两种:
注意:
当mask参数不为None时,它是一个宽和高比image都大两个像素的单通道8位图像。image图像中的像素
(x,y)与mask中(x+1,y+1)的对应。填充只针对mask中的值为0的像素进行。进行填充之后,mask中所有
被填充的像素将被赋值为1。如果只希望修改mask,而不对原始图像进行填充,可以开启flags标志中的
FLOODFILL_MASK_ONLY。
当mask参数为空时,所有的填充操作在原图上进行,会修改原图,可见下面示例。
import cv2
import matplotlib.pyplot as pl
pl.rcParams["font.family"] = "SimHei"#直接修改配置字典,设置默认字体
img = cv2.imread("E:/ruanjianDM/jupyternoerbookDM/LYF.jpg")
seed1 = 344, 188
seed2 = 187, 144
diff = (13, 13, 13)
fig, axes = pl.subplots(1, 3, figsize=(9, 4))
axes[0].set_title("原图")
axes[0].imshow(img)
h, w = img.shape[:2]#(1008, 720, 3) h=1008,w=720
mask = np.zeros((h+2, w+2), np.uint8)#mask大小(1010, 722),无符号八位整型,表示范围是[0, 255]的整数
cv2.floodFill(img, mask, seed1, (0, 0, 0), diff, diff, cv2.FLOODFILL_MASK_ONLY)
axes[1].set_title("黑色填充")
axes[1].imshow(~mask, cmap="gray")
cv2.floodFill(img, None, seed2, (0, 255, 0), diff, diff)
axes[2].set_title("绿色填充")
axes[2].imshow(img)
使用inpaint()可以从图像上去除指定区域中的物体,可以用于去除图像上的水印、划痕、污渍等瑕疵。
它的调用参数如下:
inpaint(src, inpaintMask, inpaintRadius, flags[, dst])
src参数是原始图像,
inpaintMask参数是大小和src相同的单通道8位图像,其中不为0的像素表示需要去除的区域。
dst参数用于保存处理结果。
inpaintRange参数是处理半径,半径越大处理时间越长,结果越平滑。
flags参数选择inpaint的算法,目前有两个候选算法:INPAINT_NS和INPIANT_TELEA。
import cv2
import matplotlib.pyplot as pl
pl.rcParams["font.family"] = "SimHei"#直接修改配置字典,设置默认字体
img = cv2.imread("E:/ruanjianDM/jupyternoerbookDM/LYF.jpg")
imgg = cv2.imread("E:/ruanjianDM/jupyternoerbookDM/LYF.jpg",0)
imge1,imge2=cv2.threshold(imgg,80,255,cv2.THRESH_BINARY_INV)#灰度图转二值图
dst = cv2.inpaint(img, imge2, 3, cv2.INPAINT_TELEA)#基于快速行进算法
dst1 = cv2.inpaint(img, imge2, 3, cv2.INPAINT_NS)#基于流体动力学并使用了偏微分方程
fig, axes = pl.subplots(1, 3, figsize=(9, 4))
axes[0].set_title("原图")
axes[0].imshow(img)
axes[1].set_title("基于快速行进算法")
axes[1].imshow(dst)
axes[2].set_title("基于流体动力学")
axes[2].imshow(dst1)