一些小tips:
文件夹必须这样放
一、图像的基本操作
1.jupyter中 shift+回车 可以直接运行一个模块
1.2灰度图中0最暗是黑色 255最亮
1.5
1 import cv2
2 import matplotlib.pyplot as plt
3 import numpy as np
4 %matplotlib inline
这些是常用的包的导入
2.import as的用法:
在Python中,如果import的语句比较长,导致后续引用不方便,可以使用as语法,比如:
import dir1.dir2.mod
# 那么,后续对mod的引用,都必须是dir1.dir2.mod
dir1.dir2.mod.X
那么,为了简化输入,可以使用as语法:
import dir1.dir2.mod as m
# 那么,后续对mod的引用,可以直接使用m
m. X
# 需要注意的是,使用as语法之后,只能通过as后面名字来访问导入的moudle
import mod as m
m.X # OK
mod.X # Error
3.opencv默认读取图片格式是BGR(三通道,范围是0-255)
4.读取图像命令
img=cv2.imread('cat.jpg') 读取图片 并且是本路径下的图片
5.显示图像命令
cv2.imshow=('img1',img) 第一个参数是显示图像必须要临时取的名字,第二个是图像的定义名(即4中的img)
cv2.waitKey(数值) 即等待时间,毫秒级。例如cv2.waitKey(1000),即图片显示后等待1s,数值若为0,则表示任意键终止 配合cv2.destoryallwindows()这条使用
6
img.shape
运行此命令后显示(414,530,3) 这三个参数不固定,显示的是h w 和图片的通道数3
7.读取灰度图
img=cv2.imread(‘cat.jpg’,cv2.IMREAD_GRAYSCALE)
这时读取的就是灰度图,运行img.shape后得到的就是(414,530) 表示没有三通道了,只是一个灰度图
8.保存图片
cv2.imwrite(‘mycat.png’,img)
9.img.size运行后得到20700表示此图片有20700个像素点组成
10.img.dtype运行后得到dtype(‘uint8’)表示储存这些像素值的数字数uint8类型。 dtype表示数据类型
11.读取视频的一系列操作
vc=cv2.VideoCapture('test.mp4') #传入视频流
#检测视频能否正常读取
if vc.isOpened():
open,frame=vc.read()
else:
open=False
其中read()是一帧一帧地读取视频中的图片,返回的第一个参数open是bool值,用来表示视频是否正常打开,第二个参数frame保存的是每一帧的图片
while open:
ret,frame=vc.read()
if frame is None:
break;
if ret==true;
gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #把frame传进去进行2gray的操作
cv2.imshow('result',gray)
if cv2.waitKey(10) & 0xFF==27: #27代表ESC ,waitKey代表处理完一帧等待的时间(毫秒级)
break;
vc.release()
cv2.destroyALLWindows()
12.截取部分图像
img=cv2.imread(‘cat.jpg’)
cat=img[0:50,0:200]
cv2.imread('cat',cat)
13.颜色通道提取
b.g.r=cv2.split(img)执行后再执行一个单独的
r
就会显示r通道上所有的像素值
r.shape运行后得到的还是(414,500) 两外俩结果也一样 都跟灰度图的h w保持一致
分开之后可以把这三种颜色merge在一起
img=cv2.merge(b,g,r)
然后运行img.shape之后得到还是(414,500,3)的结果,表明三通道重新merge在一起了
14.只保留R通道
cur_img=img.copy()
cur_img[:,:,0]=0 #第一个通道即B通道所有数为0
cur_img[:,:,1]=0 #第二个通道即G通道所有数为0
cv2.imread('R',cur_img) #显示只有R通道时候的图像
##只保留G通道的代码
cur_img=img.copy()
cur_img[:,:,0]=0 #第一个通道即B通道所有数为0
cur_img[:,:,2]=0 #第二个通道即R通道所有数为0
cv2.imread('G',cur_img) #显示只有G通道时候的图像
15.边界填充
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)
reflect=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,borderType=cv2.BORDER_REFLECT)
reflect101=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,borderType=cv2.BORDER_REFLECT_101)
wrap=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,borderType=cv2.BORDER_WRAP)
constant=cv2.copyMakeBorder(img,top_size,bottom_size,left_size,right_size,borderType=cv2.BORDER_CONSTANT,value=0)
以上为五种边界填充的方法:
BORDER_REPLICATE:复制法,直接复制最边缘的像素
BORDER_REFLECT:反射法,对图像中的像素在两边进行复制 如:fedcba|abcdefgh|hgfedcb
BORDER_REFLECT_101:反射法,以最边缘的像素为轴进行反射 如:fedcb|abcdefgh|gfedcb 相对上面这种方法少了重复的部分,会更加的对称,看上去更加自然一点
BORDER_WRAP:外包装法 如cdefgh|abcdefgh|abcdefg 这个还有些不太懂。。。
BORDER_CONSTANT:常量法,使用常数值填充,会多一个value变量
#显示图像
import matplotlib.pyplot as plt
plt.subplot(231),plt.imshow(img,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
...
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
就会六张显示在同一张图中
subplot中的三个参数 第一个表示行数 第二个表示列数 第三个表示是第几张图片
16.数值计算
img_cat=cv2.imread('cat.jpg')
img_dog=cv2.imread('dog.jpg') #先读两张图
img_cat2=img_cat+10 #假如图像中为500*500 且是rgb三通道的,那么此操作意味在500*500*3的每一个数都加10
img_cat+img_cat2 #若使用img_cat+img_cat2操作,如果计算结果超过了255,那么其显示结果不会超过255,相当于%256
opencv提供了cv2.add函数
cv2.add(img_cat,img_cat2) #opencv提供了cv2.add函数则是不同算法,如果计算结果大于255的话不会像上述计算过程一样取余,超过255的结果就按照255来,没越界就用计算结果
img_cat+img_dog #图像融合操作,前提是两张图片size一样
img_dog=cv2.resize(img_dog,(500,414)) #输入想变换的图像 resize操作 参数顺序为w h
执行img_dog.shape之后显示的是(414,500,3)#显示的是h w 通道数
res=cv2.resize(img,(0,0),fx=3,fy=1) #w h参数为0 0表示不指定具体参数,后面多的fx表示为原图w的多少倍,fy表示h为原图的多少倍 即像素数为原来的几倍
res=cv2.addWeighted(img_cat,0.4,img_dog,0.6,0) #R=X*0.4+Y*0.6+b 这五个参数分别是x 0.4 y 0.6 0(偏置项) 偏置项一般来提亮或者变暗
二、阈值与平滑处理
1.图像阈值
ret, dst = cv2.threshold(src, thresh, maxval, type) #等号左边的两个返回参数第一个还不知道什么用,第二个参数可以改成你想要的图像名字
src: 输入图,只能输入单通道图像,通常来说为灰度图
dst: 输出图
thresh: 阈值 常用的有127
maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值 灰度的话这个值设为255
type:二值化操作的类型,包含以下5种类型: cv2.THRESH_BINRY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV
cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
cv2.THRESH_BINARY_INV THRESH_BINARY的反转
cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
2.图像平滑
#均值滤波
#简单的平均卷积操作
blur = cv2.blur(img, (3, 3)) #img表示被处理的图像 (3,3)表示核的尺寸,通常用奇数*奇数 blur是均值滤波函数
#方框滤波
#可以选择归一化 归一化之后和均值滤波一样
box=cv2.boxFilter(img,-1,(3,3),normalize=True) #img是原图 -1基本不会变 (3,3)是核尺寸 true表示选择了归一化(3*3个像素值加载一起要除以9)
cv2.imshow('box',box)
cv2.WaitKey(0)
cv2.destoryAllWindows()
#选择不归一化(所有超过255的越界值最后赋予255)
box=cv2.boxFilter(img,-1,(3,3),normalize=False) #img是原图 -1基本不会变 (3,3)是核尺寸 False表示没有选择归一化(没有处以9)
cv2.imshow('box',box)
cv2.WaitKey(0)
cv2.destoryAllWindows()
#高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
aussian = cv2.GaussianBlur(img, (5, 5), 1) #最后一个参数是sigma标准差
cv2.imshow('aussian',aussian)
cv2.WaitKey(0)
cv2.destoryAllWindows()
#中值滤波
# 相当于用中值代替*
median = cv2.medianBlur(img, 5) #表示核尺寸是5*5的,选择第13大的数字(中值、中位数)
#展示所有
res=np.hstack((blur,aussian,median)) #hstack是横着展示 horizontal 水平的 vstack是竖着展示 vertial垂直的;注意双层括号
cv2.imshow('median vs average',res)
cv2.WaitKey(0)
cv2.destoryAllWindows()
三、腐蚀(erode)操作(去毛刺)和膨胀(dilate)操作
核尺寸越大 可能被腐蚀的范围就越多(因为核越大 那么核边界接触背景的时候,核中心很靠内)
#腐蚀
img=cv2.imread('dige.jpg')
cv2.imshow('img',img)
cv2.WaitKey(0)
cv2.destoryAllWindows()
kernel=np.ones((5,5),np.uint8) #定义核 尺寸5*5 全是数字1 类型是uint8
erosion = cv2.erode(img,kernel,iterations = 1) #iterations 迭代次数
cv2.imshow('erosion',erosion)
cv2.WaitKey(0)
cv2.destoryAllWindows() #显示图像
#迭代次数的不同
#膨胀(与腐蚀互为逆运算)
kernel = np.ones((3,3),np.uint8)
dige_dilate = cv2.dilate(dige_erosion,kernel,iterations = 1) #膨胀
#膨胀次数的不同
#开运算与闭运算
开运算:先腐蚀,后膨胀
闭运算:先膨胀,后腐蚀
#开运算(先干掉毛刺,再恢复原状)
img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('opening', opening)
cv2.waitKey(0)
cv2.destroyAllWindows()
开运算之后
#闭运算
img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('closing', closing)
cv2.waitKey(0)
cv2.destroyAllWindows()
闭运算之后(毛刺膨胀之后变粗就不容易再腐蚀掉了)
#梯度运算
##梯度=膨胀-腐蚀
pie = cv2.imread('pie.png')
kernel = np.ones((7,7),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.waitKey(0)
cv2.destroyAllWindows() #同时显示膨胀和腐蚀的效果
##梯度运算代码
gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel) ##减小kernel大小可以使得边界更细更精确
cv2.imshow('gradient', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows() #运行结果是膨胀-腐蚀
#礼帽和黑帽
礼帽=原始输入-开运算结果
黑帽 = 闭运算-原始输入
#礼帽 礼帽得到的是刺 即被腐蚀的部分 保留刺 去掉图
img = cv2.imread('dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
cv2.waitKey(0)
cv2.destroyAllWindows()
#黑帽 剩下原图的一些轮廓 刺-刺=没了
img = cv2.imread('dige.png')
blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat ', blackhat )
cv2.waitKey(0)
cv2.destroyAllWindows()
#边缘检测
sobel算子
img=cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
ddepth:图像的深度
dx和dy分别表示水平和竖直方向
ksize是Sobel算子的大小
#这个算子算出来小于0的数全截断为0了
##注释下ddepth的意义
图像深度是指存储每个像素所用的位数,也用于量度图像的色彩分辨率.图像深度确定彩色图像的每个像素可能有的颜色数,或者确定灰度图像的每个像素可能有的灰度级数.它决定了彩色图像中可出现的最多颜色数,或灰度图像中的最大灰度等级.比如一幅单色图像,若每个象素有8位,则最大灰度数目为2的8次方,即256.一幅彩色图像RGB3个分量的象素位数分别为4,4,2,则最大颜色数目为2的4+4+2次方,即1024,就是说像素的深度为10位,每个像素可以是1024种颜色中的一种.
例如:一幅画的尺寸是1024*768,深度为16,则它的数据量为1.5M。
计算如下:102476816bit=(102476816)/8字节=[(102476816)/8]/1024KB={[(102476816)/8]/1024}/1024MB。
深度组合
输入深度(src.depth()) 输出深度(ddepth)
CV_8U -1 / CV_16S / CV_32F / CV_64F
CV_16U / CV_16S -1 / CV_32F / CV_64F
CV_32F -1 / CV_32F / CV_64F
CV_64F -1 / CV_64F
注意
当ddepth = -1时,输出图像将具有与源相同的深度。
##################
sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #cv2.CV_64F 正负都能表示
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows() ##定义一个显示函数
cv_show(sobelx,'sobelx')
上图右边是缺失的;白(255)到黑(0)是正数,黑到白就是负数了,所有的负数会被截断成0,所以要取绝对值
sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) ##这个函数可能在不同版本中升级可能会直接呈现下面图中的结果
sobelx = cv2.convertScaleAbs(sobelx) #绝对值
cv_show(sobelx,'sobelx')
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely) #求绝对值
cv_show(sobely,'sobely') #计算y方向的
分别算出Gx和Gy之后,可以算出G 一般G=根号下(Gx^2+Gy^2) 或者G=|Gx|+|Gy|
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0) #dst = src1 * alpha + src2 * beta + gamma;
cv_show(sobelxy,'sobelxy')
分别计算x和y,再求和;不建议直接计算(效果不如分开计算)
sobelxy = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy) #求绝对值
cv_show(sobelxy,'sobelxy') #同时计算xy方向的
#Scharr算子
数值比sobel算子更大一些 对结果的差异更明显更敏感一些(上图中Gy写错了 下面三行应该是+3 +10 +3)
#laplacican算子
并不会单独去使用,因为拉普拉斯算子对噪音更加敏感一些,会跟其他工具结合使用
#不同算子的差异
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy,scharrxy,laplacian))
cv_show(res,'res')
scharry效果更好
#Canny边缘检测
1.使用高斯滤波器,以平滑图像,滤除噪声。
2.计算图像中每个像素点的梯度强度和方向。
3.应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
4.应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
5.通过抑制孤立的弱边缘最终完成边缘检测。
###梯度值在minVal和maxVal中间的时候,如果连有边界,那么就认为其是边界####
img=cv2.imread("lena.jpg",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,80,150) #minVal80,maxVal150 当minVal较小的时候,降低了淘汰标准,意为检测出的边界越多越好,但是可能检测出来的可能不只是边 界还有一些其他的,当minVal较大的时候,只有该点极有可能是边界的情况下才会被保留。。maxVal同理
v2=cv2.Canny(img,50,100)
res = np.hstack((v1,v2))
cv_show(res,'res')
参数不同得到的结果 参数越小得到的纹理越多 因为阈值上面的都是留下来的点
#图像处理
图像金字塔
高斯金字塔 拉普拉斯金字塔
高斯金字塔:向下采样方法(缩小)从下往上 相当于卷积 #向下采样当中向下的意思是图像大小慢慢变小
将偶数行和列去除后会使得800*800的图像变为400*400的图像 大小变为原来的1/4
高斯金字塔:向上采样方法(放大)
注意:放大和缩小都会带来部分信息丢失
img=cv2.imread("AM.png")
cv_show(img,'img')
print (img.shape)
结果是(442, 340, 3)
图片显示为
up=cv2.pyrUp(img) #pyramid 金字塔 放大
cv_show(up,'up')
print (up.shape)
结果显示为(884, 680, 3)
down=cv2.pyrDown(img)
cv_show(down,'down')
print (down.shape)
结果显示为(221, 170, 3)
拉普拉斯金字塔
Gi表示原来的图
过程图为可以连续操作好几次的过程图
down=cv2.pyrDown(img)
down_up=cv2.pyrUp(down)
l_1=img-down_up
cv_show(l_1,'l_1')
#图像轮廓
cv2.findContours(img,mode,method) 轮廓是一个整体
mode:轮廓检索模式 Contours轮廓
RETR_EXTERNAL :只检索最外面的轮廓;
RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次; (最常用)
method:轮廓逼近方法(最常用两种)
####第二种用四个点就可以表示一个长方形
为了更高的准确率,使用二值图像,轮廓检测之前要把图像化为二值图像
img = cv2.imread('car.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) #二值处理
cv_show(thresh,'thresh')
显示结果为下图(二值图像)
#binary二值后图片;contours数据点是保存轮廓的信息 使用np.array(contours)可以看到;hierarchy表示层级
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) #新版opencv好像得把binary去掉就可以了#opencv2返回contours, hierarchy,opencv3返回binary, contours, hierarchy
#绘制轮廓
#传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
# 注意需要copy,要不原图会变
draw_img = img.copy() #先copy一下,因为下面的.drawContours函数会把它处理的图像变化一下并保存下来 叫做传递对象的引用 python不少函数这样子
#-1代表所有轮廓 也可以为0 此时会显示第一个轮廓; (0, 0, 255)红色BGR; 2 线条宽度
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv_show(res,'res')
显示结果如下图
#轮廓特征
cnt = contours[0] #第一个轮廓 contours为上面计算出来的
#面积
cv2.contourArea(cnt)# 8500.5
#周长,True表示闭合的
cv2.arcLength(cnt,True)#437.9482651948929
想要计算上述特征的话需要计算各个轮廓的特征
#轮廓近似
#轮廓近似原理
AB曲线中找到一点C 使得该点到直线AB的距离d1最大,看下d1是否超过了界限T,如果没有超过那么AB弧会被线段AB代替,如果超过了T,那么分别按照这种方法来分析AC弧和BC弧,即对于AC弧观察d2是否超过了T,BC弧同理。
img = cv2.imread('contours2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
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)
cv_show(res,'res')
epsilon = 0.15*cv2.arcLength(cnt,True) # 0.15这个系数越大,轮廓近似图越小
approx = cv2.approxPolyDP(cnt,epsilon,True) #第一个参数是某一个轮廓;第二个参数是一个值,就是上述界限T,一般按照周长的一个比例来做;第三个参数
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show(res,'res')
#边界矩形 (即包含住图形的最小矩形)
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
x,y,w,h = cv2.boundingRect(cnt) # #这个函数很简单,img是一个二值图,也就是它的参数;返回四个值,分别是x,y,w,h;x,y是矩阵左上点的坐标,w,h是矩阵的宽和高,然后利用cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)画出矩形
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) ##rectangle用一个最小的矩形,把找到的形状包起来
cv_show(img,'img')
#计算一些特征 比如面积比
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print ('轮廓面积与边界矩形比',extent)
#out:0.5154317244724715
#外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
cv_show(img,'img')
#模板匹配
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
TM_CCORR:计算相关性,计算出来的值越大,越相关
TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
尽量用带归一化的结果
公式:匹配公式https://docs.opencv.org/3.3.1/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d
# 模板匹配
img = cv2.imread('lena.jpg', 0)
template = cv2.imread('face.jpg', 0)
h, w = template.shape[:2]
#img.shape (263, 263)
#template.shape (110, 85)
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
res.shape #模板匹配结果个数(154, 179),即原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res) #相当于四个参数分别是模板与原图最小差值,最大差值以及他们所在的位置,这个min_loc位置是用那个框的左上角的点来表示的,知道模板的长度宽度就可以画出这个框
运行min_val得到39168.0
运行max_val得到74403584.0
运行min_loc得到(107,89) 左上角为原点 从左到右 从上到下
运行max_loc得到(159,62)
#六种结果的差异图
for meth in methods:
img2 = img.copy()
# 匹配方法的真值
method = eval(meth)
print (method)
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 画矩形
cv2.rectangle(img2, top_left, bottom_right, 255, 2)
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.xticks([]), plt.yticks([]) # 隐藏坐标轴
plt.subplot(122), plt.imshow(img2, cmap='gray')
plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
带归一化的操作结果一般不会差
匹配多个对象
img_rgb = cv2.imread('mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.jpg', 0)
h, w = template.shape[:2]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8 #指定的阈值
# 取匹配程度大于%80的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]): # *号表示可选参数
bottom_right = (pt[0] + w, pt[1] + h)
cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2) #网友注:loc[::-1]可以反转获取到坐标点顺序,zip(*A)可以把A元组转为二维矩阵,因为np.where返回的是元祖
cv2.imshow('img_rgb', img_rgb)
cv2.waitKey(0)
#out -1
#直方图和傅里叶变换
cv2.calcHist(images,channels,mask,histSize,ranges)
import cv2 #opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt#Matplotlib是RGB
%matplotlib inline
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('cat.jpg',0) #0表示自动转为灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
# hist.shape (256, 1)
plt.hist(img.ravel(),256); #img.ravel是多维矩阵变一维数组
plt.show()
img = cv2.imread('cat.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
Mask(掩码)操作
# 创建mask
mask = np.zeros(img.shape[:2], np.uint8) #[:2]表示取前两个值,[:]代表取所有值,shape是一维数组,取前两个参数w,h
print (mask.shape)
mask[100:300, 100:400] = 255 #255是默认要求,范围是可变的
#cv_show(mask,'mask') (414, 500)
img = cv2.imread('cat.jpg', 0)
masked_img = cv2.bitwise_and(img, img, mask=mask) #与操作
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0, 256])
plt.show()
#1 为原图 2 为mask图 3 为masked_img 4为直方图
直方图均衡化
下图左为原始像素点,右边为映射后的像素点
累积概率相当于概率密度(小于等于改值);映射之后间距变小,其真正含义是均衡化希望对于不同的自变量值,自变量每向前走一步,步长一定的情况下,因变量的增量不会相差太远。
原图&&均衡化
img = cv2.imread('clahe.jpg',0) #0表示灰度图 #clahe
plt.hist(img.ravel(),256);
plt.show()
equ = cv2.equalizeHist(img) #均衡化函数
plt.hist(equ.ravel(),256)
plt.show()
右边图均衡化之后丢失了部分细节,看起来更清晰
自适应直方图均衡化
分成很多小块各自的进行均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clahe = clahe.apply(img)
res = np.hstack((img.equ.res_clahe))
cv_show(res,'res')
分别是原始图像、全局均衡化、自适应均衡化。想要保留一些细节就要自适应,但是受噪点影响大。
我们生活在时间的世界中,早上7:00起来吃早饭,8:00去挤地铁,9:00开始上班。。。以时间为参照就是时域分析。
但是在频域中一切都是静止的!
https://zhuanlan.zhihu.com/p/19763358
上图频域图像中 纵坐标为振幅,横坐标为频率!(这个回答看过好几次了,写的真好)
高频:变化剧烈的灰度分量,例如边界
低频:变化缓慢的灰度分量,例如一片大海
滤波
cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('lena.jpg',0) #灰度
img_float32 = np.float32(img) #转换格式
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)#低频的部分转换到中间位置
# 得到灰度图能表示的形式
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1])) #需要这样转化一下才可以得到图像形式的表达;分别对两个通道进行转换;通过*20得到0到255之间的数值
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
结果如上图,中间是低频。(掩码取中间部分,就叫低通滤波;盖住中间取外边部分,叫做高通滤波器)
#低通
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('lena.jpg',0)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置
# 低通滤波
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift) #执行idft之前要先执行ishift,留下来中间区域
img_back = cv2.idft(f_ishift) #这时候还是实部虚部的双通道的结果
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1]) #对实部虚部进行处理
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show()
#高通
img = cv2.imread('lena.jpg',0)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置
# 高通滤波 (去掉中间)
mask = np.ones((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0
# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show()
总结:高频细节,低频边界。
问题:plt中imread参数问题;[::]的问题;BORDER_WRAP;ret,dst;元组问题;#img.ravel是多维矩阵变一维数组;
hist的参数; 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1])) #需要这样转化一下才可以得到图像形式的表达,分别对两个通道进行转换,通过*20得到0到255之间的数值;