文章部分摘自 OpenCV入门学习笔记之常用的图像处理操作
文章部分来源 OpenCV从入门到实战
引入opencv
import cv2 as cv
cv2.IMREAD_COLOR
:彩色图像cv2.IMREAD_GRAYSCALE
:灰度图像src = cv.imread("1.jpg",cv2.IMREAD_COLOR) #括号里是照片地址
print(src)
# 返回np.array的结构,这些矩阵标示着BGR的值
'''
[[[198 195 187]
[198 195 187]
[198 195 187]
...
[177 168 159]
[177 168 159]
[177 168 159]]
[[198 195 187]
[198 195 187]
[198 195 187]
'''
opencv读取的格式是BGR,而不是RGB
# 图像的显示,也可以创建多个窗口
cv.imshow("input image", src)
# 等待时间,毫秒级,0表示任意键终止,如果设置未其他数,等待对应时间后图片自动关闭
cv.waitKey(0)
cv.destroyAllWindows()
我们可以将展示图片的方法写成一个函数使用
def cv_show(name,img):
cv.imshow(name, img)
cv.waitKey(0)
cv.destroyAllWindows()
.shape
方法,获取打开后的图片的形状得到的是三个数,分别代表着 h w c , 其中c指的是RGB,但顺序为BGR
当我们在imread中使用了读取类型,那灰度图就没有c了
cv.imwrite('name',src)
type(src)
.size
.dtype
cv2.videoCapture
可以捕获摄像头,用数字来控制不同的设备,例如0.1对视频的操作实际上就是将视频拆分成每一帧
vc = cv.VideoCapture('test.mp4')
# 检查是否打开正确
if vc.isOpened():
open, frame = vc.read() # 布尔值,当前帧的图像
else:
open = False
while open:
ret, frame = vc.read()
# 判断当前帧是否存在
if frame is None:
break
if ret == True
gray = cv.cvtColor(frame,cv2.COLOR_BGR2GRAY) # 转化成灰度图
cv2.imshow('result',gray)
if cv.waitKey(10) & 0xFF == 27: # 这里相当于按了esc
break
vc.release()
cv.destroyAllWindows()
img = cv.imread('xx')
cat = img[0:200,0:200]
cv_show('cat',cat)
# 提取各颜色值
b,g,r = cv.split(img)
# 获取形状
b.shape
# 各颜色组合
img = cv.merge((b,g,r))
只保留R(B,G)
cur_img = img.copy() # 克隆图片
# 根据颜色顺序BGR屏蔽通道
cur_img[:,:,0] = 0
cur_img[:,:,1] = 0
cv.show('R',cur_img)
top_size. bottom_size,left_size. right_size = (50,50,50,50)
# 复制法
replicate = cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size, borderType=cv2.BORDER_REPLICATE)
# 反射法1
reflect=cv2.copyMakeBorder(img,top_size,botton_size,left_size,right_size,cv2.BORDER_REFLECT)
# 反射法2
reflect101=cw2.copyMakeBorder(img,top_size,bottom_size, letf_size,right_size,cv2.BOFDER REFLECT_101)
# 外包装法
wrap = cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,cv2.BORDER_WRAP)
# 常量法
constant = ev2.copyMakeBorder(img, top_size,botton_size,left_size,right_size, cv2.BORDEE_CONSTANT, value=0)
img_cat = cv.imread('cat.jpg')
img_cat2 = img.cat+10
当两个图片的像素相加时,结果超过了255,则超出了范围,那么结果则是 值 = 相加值%256
cv2.add(img_cat,img_cat2)
当两个图片的像素相加时,结果超过了255,则超出了范围,那么结果则是 值 = 255
当两个尺寸不一致的图像融合,需要转化成相同的像素格式
img_dog = cv2.resize(img_dog,(500,414))
# 还可以更改比例
img_dog = cv2.resize(img_dog,(0,0),fx=1,fy=3)
# 根据权重融合 R = ax1 + ax2 + ... + b
res = cv2.addWeighted(img_cat,0.4,img_dog,0.6,0)
ret, dst = cv2.threshold(src, thresh, maxval, typo)
cv2.THRESH_BINARY;cv2.THRESH_BINARY_INV;cv2.THRESH_TRUNC;cv2.THRESH.TOZERO;cv2.THRESH_TOZERO_INV
# 简单的平均卷积操作
blur = cv2.blur(img,(3,3))
cv2.imshow('blur',blur)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 基本和均值一样,可以选择归一化
box = cv2.boxFilter(img,-1,(3,3),normalize=True) # 为true时与均值滤波一样
cv2.imshow('box',box)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
aussian = cv2.GaussianBlur(img,(5,5,1))
cv2.imshow('aussian',aussian)
cv2.waitkey(0)
cv2.destroyAllWindows()
#相当于用中值代替
median = cv2.medianBlur(img,5)
cv2.imshow('median', median)
cv2.waitKey(0)
cv2.destroyAllWindows()
res = np.hstack ((blur, aussian, median))
cv2.imshow(' median vs average' , res)
cv2.waitKey(0)
cv2.destroyAllwindows()
傅里叶滤波&&低通滤波
傅里叶变换我们知道是将图像从时域转换到频域的一种非常强大的武器, 时域中的图像数据就是我们看到的一个个像素点组成的图片,而频域中,我们是能够得到灰度分量的频率大小的。可能图像看起来不是很好理解,拿一段语言来描述最贴切:假设我们录了一段语言,里面各种声音混杂,从时域的角度,这就是按照时间序组成的一段音频,那么我们有办法把这段音频的噪声去掉,只保留重要的音频信息吗? 其实这个在时域中非常难做到,而转到频域里面,就会发现这些声音都是一条条频率不同的声音线组成,通过频率就能非常轻松的过滤出某些我们想要的声音。所以通过傅里叶变换操作,我们能非常容易的拿出图像或者声音中我们需要的某些灰度分量了。
那么回到图像里面, 同样会存在高频或者低频的灰度分量:
根据频率进行滤波,主要分为两种:
opencv中主要是cv2.dft()
和cv2.idft()
, 输入图像需要先转成np.float32格式
cv2.dft就是把时域转成了频域,但是为了显示,还需要进行逆变换,即cv2.idft()
得到的结果中频率为0的部分会在左上角, 通常要转换到中心位置, 可以通过shift
变换来实现。
cv2.dft()
返回的结果是双通道的(实部,虚部), 通常还需要转换成图像格式才能展示(0,255)
总结下低通滤波和高通滤波的过程
def dft_idft(img, threshold=30, mode='low'):
img_float = np.float32(img) # 转成float
# 时域 -> 频域
dft = cv2.dft(img_float, flags=cv2.DFT_COMPLEX_OUTPUT)
# 把低频值转到中间位置
dft_shift = np.fft.fftshift(dft) # 低频值转到中心位置
# 掩码矩阵
row, col = img.shape
c_row, c_col = int(row/2), int(col/2)
if mode == 'low':
mask = np.zeros((row, col, 2), np.uint8)
mask[c_row-threshold:c_row+threshold, c_col-threshold:c_row+threshold] = 1
elif mode == 'high':
mask = np.ones((row, col, 2), np.uint8)
mask[c_row-threshold:c_row+threshold, c_col-threshold:c_row+threshold] = 0
else:
print("参数错误")
return
# 下面就是用这个滤波器对频域的那个图像做操作
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift) # 之前是低频信息shift到了中间位置,而这个操作是从中间位置变到原来位置
img_back = cv2.idft(f_ishift) # 逆变换
img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])
return img_back
# 低通滤波和高通滤波
img_low = dft_idft(img, mode='low')
img_high = dft_idft(img, mode='high')
所谓形态学操作,我理解,对图像本身进行的一些预处理,比如让图像里面的线条变粗或者变细,重点突出图像的某些部分, 去掉图像中的毛刺,处理一些缺陷等。
图像形态学中常用的两个操作是腐蚀和膨胀,腐蚀一般用于处理毛刺问题,能够让线条或者图形变细。 而膨胀一般是填补一些缺陷, 能够让线条变粗
img = cv2.imread('img/dige.png')
cv2.imshow('img', img)
kernel = np.ones((3, 3), np.uint8)# 卷积核
erosion_img = cv2.erode(img, kernel, iterations = 1)
# 这里卷积核越大,或者迭代次数越多, 都会使得右边的线条变细。
cv2.imshow('erosion_img', erosion_img)
腐蚀算法: 用n*n的卷积核扫描每个元素,用卷积核与其覆盖的二值图像做"与"操作,如果都是1,那么结果中心元素像素值是1,否则是0
迭代次数和卷积核越大,越有利于膨胀操作, 越来越胖
# 下面尝试把上面腐蚀的图片弄成胖一点的
kernel = np.ones((3, 3), np.uint8)
dige_dilate = cv2.dilate(erosion_img, kernel, iterations=1)
cv2.imshow('dilate_img', dige_dilate)
# 对线条起了加粗的效果
膨胀算法: 猜一下,n*n的卷积核扫描每个元素, 用卷积核与其覆盖的二值图像做或操作, 如果有1, 那么中心元素变成1,如果都是0, 那么才是0
类似于一个管道把腐蚀和膨胀两个操作连接到了一起:
cv2.MORPH_OPEN
: 先腐蚀,后膨胀cv2.MORPH_CLOSE
: 先膨胀,后腐蚀img = cv2.imread('img/dige.png')
cv2.imshow('img', img)
# 开运算: 先腐蚀,再膨胀
kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('opening', opening)
# 闭运算: 先膨胀后腐蚀
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('closing', closing)
cv2.MORPH_GRADIENT
这个就是膨胀-腐蚀,容易提取到边缘信息。
# 梯度 = 膨胀 - 腐蚀
pie = cv2.imread('img/pie.png')
kernel = np.ones((5, 5), np.uint8)
dilate = cv2.dilate(pie, kernel, iterations=5)
erosion = cv2.erode(pie, kernel, iterations=5)
res = np.hstack((dilate, erosion))
cv2.imshow('res', res)
cv2.imshow('subtract', dilate-erosion)
gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel, iterations=5)
cv2.imshow('gradient', gradient)
这个梯度运算和直接膨胀-腐蚀效果是一样的结果, 背后实现,我猜其实就是膨胀先执行iterations次,然后腐蚀执行iterations次,然后前面结果减去后面的结果。
cv2.MORPH_TOPHAT
: 原始输入 - 开运算结果,即原始输入-(先腐蚀,后膨胀)cv2.MORPH_BLACKHAT
: 闭运算结果-原始输入,即(先膨胀,后腐蚀)-原始输入# 礼帽
img = cv2.imread('img/dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
# 黑帽
img = cv2.imread('img/dige.png')
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat', blackhat)
礼帽运算: 取出亮度高的地方
黑帽运算: 取出亮度低的地方
一般用的时候,原始图像转灰度图像,然后进行二值化,然后, 再通过开闭运算, 礼帽和黑帽等操作,就能拿到图像中的想要的区域来了。所以后面这些组合操作很重要,也很实用。比如车牌号识别,信用卡数字识别等,都会用到这些技术
图像的算子操作其实可以帮助我们去找图像的轮廓信息,主要有Sobel算子,Scharr算子以及Laplacian算子,区别在于卷积核参数不一样。
Sobel算子, 这感觉依然是两个卷积核进行操作, 原理如下:
这个东西其实找的是图像的轮廓信息,或者边缘信息,依赖于上面的两个卷积核,一个是水平方向的,一个是垂直方向的,实际计算的时候是这样, 3∗3的卷积核覆盖到一个图像区域,中间点的取值,就是水平的这个卷积核与当前图像卷积结果或者垂直方向的卷积核与当前图像卷积结果。这个就看是从水平上算还是垂直方向上算了。当然, 这个算子也是保证最终结果是0-255,如果超了,就会进行截断操作。
靠中心越近权重越大,所以这里用了2或者-2表示
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
'''
ddepth: 图像的深度
dx和dx分别表示水平和垂直方向, 是算x方向还是y方向
ksize是Sobel算子的大小, 一般是3*3
'''
这里直接说常规使用方法, 对于一张图片, 先通过imread的灰度方式读入进来,然后先求水平上的梯度,再求垂直方向上的梯度, 最后加权融合就能找出图片的梯度来。
lena = cv2.imread('img/lena.jpg', cv2.IMREAD_GRAYSCALE)
# 水平做sobel算子
lena_sobelx = cv2.Sobel(lena, cv2.CV_64F, 1, 0, ksize=3)
lena_sobelx = cv2.convertScaleAbs(lena_sobelx) # 这里是为了如果出现负数不让他截断成0,而是变成它的绝对值
# cv2.imshow('lena_x', lena_sobelx)
# 垂直做sobel计算
lena_sobely = cv2.Sobel(lena, cv2.CV_64F, 0, 1, ksize=3)
lena_sobely = cv2.convertScaleAbs(lena_sobely)
# cv2.imshow('lena_y', lena_sobely)
# 把这俩合并
lena_margin = cv2.addWeighted(lena_sobelx, 1, lena_sobely, 1, 0)
cv2.imshow('lena_margin', lena_margin)
这里一个小经验就是两个方向分开算,要比同时算,最终效果要好, 原因是因为,如果是同时计算的时候, 亮度上会有一些抵消。
Scharr算子能够找出更加细致的边界,使得图像的纹理信息更加丰富。使用的方法和上面Sobel是一样的,无非就是函数换了下:
# 这里用scharr算子试一下
img = cv2.imread('img/lena.jpg', cv2.IMREAD_GRAYSCALE)
lena_scharr_x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
lena_scharr_x = cv2.convertScaleAbs(lena_scharr_x)
lena_scharr_y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
lena_scharr_y = cv2.convertScaleAbs(lena_scharr_y)
lena_scharr_xy = cv2.addWeighted(lena_scharr_x, 0.5, lena_scharr_y, 0.5, 0)
cv2.imshow('lena_scharr_xy', lena_scharr_xy)
# 可以找出更细致的边界, 我猜的没错 对, 纹理细节这个词用的好
一般不会单独使用这个, 常和其他算子组合使用, 对噪声会更加敏感, 但噪音点可能不是边界,所以这个效果单独用不好
laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
cv2.imshow('laplacian', laplacian)
最后对比下各种算子找边缘的效果:
Canny边缘检测算法,用于检测图像的边缘信息。主要包括下面的流程:
这一步是对原始图像处理,去掉一些噪声,让其本身更加平滑,在Canny算法中用的是高
斯滤波器
其他滤波器,像均值滤波器,中值滤波器等都比较常用,这是滤波器那里的相关知识。
这里就是求各个中心点的梯度, Canny算法中用的是sobel算子
这里是为了去除一些梯度值很小的边缘信息, 以消除边缘信息带来的杂散效应。
线性插值法:设g1的梯度幅值Mg1),g2的梯度幅值M(g2),则dtmp1可以很得到:
M(dtmp1)=w*M(g2)+(1-w)*M(g1)
其中w=distance(dtmp1,g2)/distance(g1,g2)
distance(g1,g2)表示两点之间的距离。
这里描述下这种方法是怎么做的,这个属实有些复杂,这里是判断像素C这个点要不要被抑制点,即判断C这个点是不是极大值,可以这么做:
这个方法比上面那个简单了, 这里会借助它周围的8个点:
这里其实就是指定了一个最大值和一个最小值,然后看看中心点这个梯度值大小,根据右边的规则进行判断需要需要保留。 这里的连有边界的意思是看他是不是挨着一个边界, 如果它旁边那个点是边界了, 那么这个点也会保留下来,否则会去掉。
上面就是Canny算法各个流程的细节部分,在opencv中用起来其实很简单,只需要一个cv2.Canny函数即可。
lena = cv2.imread('img/lena.jpg', cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(lena, 80, 150)
v2 = cv2.Canny(lena, 50, 100)
res_lena = np.hstack((lena, v1, v2))
cv2.imshow('res', res_lena)
这里的minval指定的如果比较小,会找到更加细致的边界, 纹理信息更多, 但可能会拿到很多误选择的边界
maxval指定的如果比较大, 会找到更加严格的边界,但可能会漏掉一些边界
图像金字塔, 把图像组合成像金字塔一样的形状。图像金字塔的作用,比如我们要对一张图像进行特征提取, 我们可以把图像制作成一个金字塔, 对于金字塔里面的每张图片都进行特征提取, 每张图片可能提取的特征是不一样的,这样就可能增加了图像特征的丰富性。
图像金字塔是图像多尺度表达的一种,最主要用于图像分割,是一种以多分辨率来解释图像的有效但概念简单的结构,最早用于视觉和图像压缩。最底部是待处理图像的高分分辨率表示, 越高层,分辨率越低。
常见的两类金字塔:
两者的简要区别:高斯金字塔用来向下降采样图像,注意降采样其实是由金字塔底部向上,分辨率降低,它和我们理解的金字塔概念相反(注意);而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
将level0级别的图像转换为 level1,level2,level3,level4,图像分辨率不断降低的过程称为向下取样
从金字塔下往上,是一个向下采样,越采样越小, 主要过程有两步:
将level4级别的图像转换为 level3,level2,level1,leve0,图像分辨率不断增大的过程称为向上取样
金字塔上往下, 是一个向上采样, 越采样越大
它将图像在每个方向上扩大为原图像的2倍,新增的行和列均使用0来填充,并使用于“向下取样”相同的卷积核乘以4,再与放大后的图像进行卷积运算,以获得“新增像素”的新值
注意: 把图像先上采样,再下采样还原,或者是下采样再上采样还原,都会损失掉信息,虽然和原始图片一样大,但会比原始图像模糊,所以上采样和下采样都是非线性处理, 不可逆,会损失掉信息
img = cv2.imread('img/AM.png') # (442, 340, 3)
# 先向上采样看看
up_sample = cv2.pyrUp(img)
cv2.imshow('up', up_sample)
print(up_sample.shape) # (884, 680, 3)
# 向下采样
up_down = cv2.pyrDown(img)
cv2.imshow('down', up_down)
print(up_down.shape) # (221, 170, 3)
# 先经历上采样再还原
cv2.imshow('up_down', np.hstack((img, cv2.pyrDown(up_sample))))
也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。保留的是残差!为图像还原做准备,实现非常简单。
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
l_1 = img - down_up
cv2.imshow('l1', l_1)
下面,就是把高斯上采样, 下采样, 拉普拉斯金字塔的这个过程,都写成了函数的形式,可以一键把整个金字塔干出来。
# 高斯降采样金字塔
def Gaussian_Down(img, sample_times):
Gaussian_Down_pyramid = []
for i in range(sample_times):
if i == 0:
down = cv2.pyrDown(img)
else:
down = cv2.pyrDown(down)
Gaussian_Down_pyramid.append(down)
return Gaussian_Down_pyramid
def Gaussian_up(img, sample_times):
Gaussian_up_pyramid = []
for i in range(sample_times):
if i == 0:
up = cv2.pyrUp(img)
else:
up = cv2.pyrUp(up)
Gaussian_up_pyramid.append(up)
return up
def Laplace(img, sample_times):
laplace_pyramid = []
for i in range(sample_times):
if i == 0:
down = cv2.pyrDown(img)
laplace_pyramid.append(img-cv2.pyrUp(cv2.pyrDown(img)))
else:
up_down = cv2.pyrUp(cv2.pyrDown(down))
# 注意这里由于取整的关系,导致这俩还不一样大
if down.shape != up_down.shape:
laplace_pyramid.append(down - up_down[:down.shape[0], :down.shape[1], :])
else:
laplace_pyramid.append(down - up_down)
down = cv2.pyrDown(down)
return laplace_pyramid
轮廓检测和边缘检测是不一样的概念,轮廓检测是只检测对象最外面的那个大轮廓,类似于用一个框圈起对象来,相当于锁定对象的位置,而边缘检测是检测对象内部的各种边界信息。所以不一样。
轮廓检测的作用是可以帮助我们做一些额外的数值特征, 比如图像轮廓的面积,周长, 这样就能反映出大小来了。
cv2.findContours(img, mode, method) → contours, hierarchy
(返回的轮廓列表及层级信息)
img = cv2.imread('img/car.png')
# 转成灰度, 这里为啥不直接读取的时候转? 因为有些人后序会用到彩色图,仅此而已
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# 下一步, 对上面的灰度图像进行二值处理
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 像素小于127的弄成0, 大于127的弄成255,这样变成二值图像
# 下面绘制轮廓
# 最新版opencv只返回两个值了 3.2之后, 不会返回原来的二值图像了,直接返回轮廓信息和层级信息
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 下面把轮廓进行可视化
draw_img = img.copy() # 这里要copy一下, 否则会改变原始图像, 下面那哥们貌似是原子操作
# drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
# 这里的contourIdx要绘制的轮廓线,负数表示所有, 正是只画对应位置的那个点
# color是轮廓线的颜色, (0,0,255)表示红色,因为opencv里面通道排列是bgr吧应该--> 对的
# 最后面那个2是线条的宽度,值越大, 线条越宽
res = cv2.drawContours(draw_img, contours, -1, (0, 255, 0), 2)
cv2.imshow('res', res)
def contours_feature(contour):
features = []
features.append(cv2.contourArea(contour)) # 轮廓面积
features.append(cv2.arcLength(contours, True)) # 轮廓周长,后面那个是closed,表示这个曲线是否闭合
return features
controus_feature(countours[0])
# 读取图片 -> 转成灰度图 -> 二值化 -> 找轮廓
img = cv2.imread('img/contours2.png')
# 用上面的函数画一些轮廓
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv2.imshow('res', res)
# 下面进行轮廓近似
epsilon = 0.1 * cv2.arcLength(cnt, True) # 这里是指定的阈值,一般按照周长的百分比设置
# 这个阈值越大,那么画出来的轮廓就越粗糙,因为很容易直接一条直线代替曲线, 如果阈值越小,那么近似出来的轮廓细腻,因为d很容易大于阈值,用多条直线近似
approx = cv2.approxPolyDP(cnt, epsilon, True)
draw_img2 = img.copy()
res2 = cv2.drawContours(draw_img2, [approx], -1, (0, 0, 255), 2)
cv2.imshow('res2', res2)
模板匹配和卷积原理很像, 模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里面有6种, 然后将每次计算的结果放入一个矩阵里,作为结果输出。
假如原图是A ∗ B ABA∗B大小, 而模板是a ∗ b aba∗b大小, 则输出结果的矩阵( A − a + 1 ) ( B − b + 1 ) (A-a+1)(B-b+1)(A−a+1)(B−b+1), 这个感觉和卷积神经网络中的卷积操作很像了。 主要步骤如下:
具体计算公式可以去查文档。尽量使用下面归一化的结果
# 1. 读入原始图像,灰度图模式读入 0表示灰度图,还有就是cv2.IMREAD_SCALE
img = cv2.imread('img/cat.jpg', 0) # <==> cv2.imread('img/cat.jpg', cv2.IMREAD_GRAYSCALE)
# 2. 读入模板
template = cv2.imread('img/template.jpg', 0)
# 3. 模板匹配
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF) # 值越小越相似
res.shape # (548-122+1)(666-181+1)
# 4. 获取匹配好图像的起始位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 下面尝试把矩形框画出来
top_left = min_loc
bottom_right = (top_left[0]+template.shape[0], top_left[1]+template.shape[1])
# 画矩形
img2 = img.copy()
rect = cv2.rectangle(img2, top_left, bottom_right, 255, 2)
假设图片里面不只是一个对象能匹配呢? 有没有办法把所有的模板图像都匹配出来, 那就需要自己最大或者最小位置的地方了,其实也简单:
img_rgb = cv2.imread('img/mario.jpg')
# 转成灰度图
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('img/mario_coin.jpg', 0)
h, w = template.shape[:2]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8 # 这里设置一个门限
# 取匹配程度大于百分之八十的坐标
loc = np.where(res >= threshold)
# 这个loc是横坐标一个数组,纵坐标一个数组的形式,所以下面得用可选参数的方式进行组合
for pt in zip(*loc[::-1]):
bottom_right = (pt[0]+w, pt[1]+h)
cv2.rectangle(img_rgb, pt, bottom_right, (0, 255, 0), 2)
cv2.imshow('img_rgb', img_rgb)
cv2.calcHist(images, channels, mask, histSize, ranges):
img_bgr = cv2.imread('img/cat.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
histr = cv2.calcHist([img_bgr], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.xlim([0, 256])
mask掩码操作的使用, mask掩码矩阵传入之后,只会统计我们需要部分的直方图:
# 先创建mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
cv2.imshow('mask', mask) # 这样就创造除了一个掩码矩阵, 只有0和255组成, 白色的地方是要显示的,黑色的地方是被遮挡
masked_img = cv2.bitwise_and(img, img, mask=mask) # 与操作 等价img & mask
# 下面对比一下
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
直方图的作用,就是可以对一些图像均衡化处理,使得图像的对比度拉高,更加清晰。
img = cv2.imread('img/clahe.jpg', 0)
plt.hist(img.ravel(), 256)
# 均衡化
equ = cv2.equalizeHist(img)
plt.hist(equ.ravel(), 256)
plt.show()
res = np.hstack((img, equ))
cv2.imshow('res', res) # 会发现均衡话之后,变得更加清晰了