大四毕业后的这个暑假正式开始学习openCV
参考教程:唐宇迪老师: https://www.bilibili.com/video/BV1tb4y1C7j7
1.图像梯度-Sobel算子
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
ddepth:图像的深度,通常是-1
dx和dy分别表示水平和竖直方向
ksize: 是Sobel算子的大小
demo1:
import cv2
img = cv2.imread('D:\\openCV files\\data\\1\\pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #cv2.CV_64F表示结果可以带负数(防止被截断),1,0表示只要水平的,不要竖直的
cv_show(sobelx,'sobelx')
#由于算子是右边减去左边,白减去黑是整数,黑减去白则是负数,所有的负数会被截断为0,所以要取绝对值
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #cv2.CV_64F表示结果可以带负数,1,0表示只要水平的,不要竖直的
sobelx = cv2.convertScaleAbs(sobelx)
cv_show(sobelx,'sobelx')
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) #cv2.CV_64F表示结果可以带负数,0,1表示只要竖直的
sobely = cv2.convertScaleAbs(sobely)
cv_show(sobely,'sobely')
#分别计算x和y,再求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
#不建议直接计算
sobelxy = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3) #水平竖直都设成1
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show(sobelxy,'sobelxy')
img = cv2.imread('D:\\openCV files\\data\\1\\lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #cv2.CV_64F表示结果可以带负数,1,0表示只要水平的,不要竖直的
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) #cv2.CV_64F表示结果可以带负数,0,1表示只要竖直的
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
#整体计算结果很模糊!
img = cv2.imread('D:\\openCV files\\data\\1\\lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelxy = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3) #水平竖直都设成1
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show(sobelxy,'sobelxy')
import cv2
import numpy as np
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('D:\\openCV files\\data\\1\\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)
#Scharr算子细节更丰富一些
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算子细节不太丰富,一般不单独使用
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy,scharrxy,laplacian)) #注意这里一直是双括号
cv_show(res,'res')
out2:
3.Canny边缘检测
(1)使用高斯滤波器,以平滑图像,滤除噪声。
(2)计算图像中每个像素点的梯度强度和方向。
(3)应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
(4)应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
(5)通过抑制孤立的弱边缘最终完成边缘检测。
import cv2
import numpy as np
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('D:\\openCV files\\data\\1\\lena.jpg',cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img, 80, 150) #80:minVal 150:maxVal
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((v1, v2))
cv_show(res, 'res')
img = cv2.imread('D:\\openCV files\\data\\1\\car.png',cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img, 120, 250) #80:minVal 150:maxVal
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((v1, v2))
cv_show(res, 'res')
demo4:
import cv2
import numpy as np
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('D:\\openCV files\\data\\1\\AM.png')
cv_show(img,'img')
print(img.shape)
#高斯金字塔
#向上采样
up = cv2.pyrUp(img)
cv_show(up,'up')
print(up.shape)
#向下采样
down = cv2.pyrDown(img)
cv_show(down,'down')
print(down.shape)
#继续向上采样
up2 = cv2.pyrUp(up)
cv_show(up2,'up2')
print(up2.shape)
#先向上采样,再向下
up = cv2.pyrUp(img)
up_down = cv2.pyrDown(up) #并没有恢复原图
cv_show(np.hstack((img,up_down)),'comparison')
cv_show(img-up_down,'img-up_down') #做减法发现仍有区别
#拉普拉斯金字塔
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
l_l = img - down_up
cv_show(l_l,'l_l')
cv2.findContours(img,mode,method)
mode:轮廓检索模式
RETR_EXTERNAL:只检索最外面的轮廓。
RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中。
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层,顶层是各部分的外部边界,第二层是空洞的边界。
RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。(最常用)
method:轮廓逼近方法
CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CHAIN_APPROX_SIMPLE:以压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
import cv2
import numpy as np
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('D:\\openCV files\\data\\1\\contours.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转灰度图
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) #阈值函数,二值化为0和255
cv_show(thresh,'thresh')
#binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) 旧版本的open CV返回三个值
#binary就是二值化的图像结果
#contours是轮廓点结果
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 新版本的open CV返回两个值
print(np.array(contours).shape)
#hierarchy 层级
#绘制轮廓
#传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
#注意需要copy,防止改变原图
draw_img = img.copy()
#画轮廓,在原图像img上绘制,contours表示被画轮廓,-1表示默认把所有的轮廓都画进来,(0, 0, 255)表示BGR,此处用红色线条绘制;2是线条的宽度
res = cv2.drawContours(draw_img, contours, -1,(0, 0, 255), 2)
cv_show(res,'res')
draw_img = img.copy()
res1 = cv2.drawContours(draw_img, contours, 0,(0, 0, 255), 2) #把-1换成0,三角形外轮廓被画出
draw_img = img.copy()
res2 = cv2.drawContours(draw_img, contours, 1,(0, 0, 255), 2) #把-1换成1,三角形内轮廓被画出
cv_show(res1,'res1')
cv_show(res2,'res2')
#轮廓特征
cnt = contours[0] #算轮廓特征需要先把具体的轮廓拿出来
#面积
print(cv2.contourArea(cnt))
#周长,True表示闭合的
print(cv2.arcLength(cnt,True))
#轮廓近似
img = cv2.imread('D:\\openCV files\\data\\1\\contours2.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转灰度图
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) #阈值函数,二值化为0和255
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 新版本的open CV返回两个值
cnt = contours[0] #算轮廓特征需要先把具体的轮廓拿出来
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1,(0, 0, 255), 2)
cv_show(res,'res')
epsilon = 0.1*cv2.arcLength(cnt,True) #0.1倍的周长作为近似的阈值,0.1越小,得到的近似轮廓越精准
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1,(0, 0, 255), 2)
cv_show(res,'res')
#边界矩形
img = cv2.imread('D:\\openCV files\\data\\1\\contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #转灰度图
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) #阈值函数,二值化为0和255
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 新版本的open CV返回两个值
cnt = contours[0] #算轮廓特征需要先把具体的轮廓拿出来
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
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)
#外接圆
(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')
out5:
D:/PyCharm files/openCV/图像轮廓.py:18: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
print(np.array(contours).shape)
(11,)
8500.5
437.9482651948929
轮廓面积与边界矩形比 0.5154317244724715
补充知识点:
cv2.imread(filename, flags=None)
参数:
filename: 图像地址 如: ‘./xxx.png’
flags: 标志位, 表示读取数据的格式,读取彩色可以设为cv2.IMREAD_COLOR(flags=1)
读取灰度图像设为cv2.IMREAD_GRAYSCALE(flags=0),
读取原始图像设为cv2.IMREAD_UNCHANGED(flags=-1)。
模块匹配和卷积原理很像,模块在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图像是A×B大小,而模板是A×b大小,则输出结果的矩阵是(A-a+1)×(B-b+1)。
TM_SQDIFF:计算平方不同,计算出来的值越小,越相关‘
TM_CCORR:计算相关性,计算出来的值越大,越相关
TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
(以下三种更推荐:)
TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
demo6:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
#模块匹配
img = cv2.imread('D:\\openCV files\\data\\2\\lena.jpg',0) #才发现openCV文件路径里不允许有中文路径
template = cv2.imread('D:\\openCV files\\data\\2\\face.jpg',0) #flags=0,表示读取灰度图像
h,w = template.shape[:2] #[:2]表示切片,第0个和第1个,高度110,宽度85
print(img.shape)
print(template.shape)
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF) #TM_SQDIFF让我们关注最小值,即min_loc
print(res.shape)
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) #最小值,最大值,最小值坐标位置,最大值坐标位置
#minMaxLoc的结果就是列在前,行在后,相当于w前h后
print(min_val)
print(max_val)
print(min_loc) ##方框左上角的那个点的位置
print(max_loc)
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('D:\\openCV files\\data\\2\\mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('D:\\openCV files\\data\\2\\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)
#[::-1],,,loc结果是高度坐标一起在前面列表(行在前),宽度坐标一起在后一个列表(列在后),所以需要倒序,使得宽在前,高在后,zip将单一的坐标两两打包在一起
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)
cv_show(img_rgb, 'img_rgb')
out6:
(263, 263)
(110, 85)
(154, 179)
39168.0
74403584.0
(107, 89)
(159, 62)
4
5
2
3
0
1
cv2.calcHist(images, channels, mask, histSize, ranges)
images:原图像格式为uint8或float32.当传入函数时应用中括号[],例如[img]
channels:同样用中括号。如果传入图像是灰度图,它的值是[0],如果是彩色图像,传入的参数可以是[0][1][2],他们分别对应着BGR
mask:掩膜图像。统整幅图像的直方图把它为None.但是如果你想统计图像某一部分的直方图,你就制作一个掩膜图像并使用它
histSize:BIN的数目,也用中括号,比如0-10,11-20,每十个一统计,这个参数指定bin(箱子)的个数,也就是总共有几条条状图
ranges:像素值的范围常为[0256]
demo7:
import cv2
import numpy as np
import matplotlib.pyplot as plt #matplotlib是RGB
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('D:\\openCV files\\data\\2\\cat.jpg',0)#0表示灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
print(hist.shape)
plt.hist(img.ravel(),256) #ravel()多维降为一维,一共256个BIN
plt.show()
img = cv2.imread('D:\\openCV files\\data\\2\\cat.jpg')
color = ('b','g','r')
for i,col in enumerate(color):#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
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表示二维,不需要将颜色通道加进来;uint8:0-255
mask[100:300, 100:400] = 255 #要保存什么部分,就置为白色,255
cv_show(mask,'mask')
img = cv2.imread('D:\\openCV files\\data\\2\\cat.jpg',0)#0表示灰度图
cv_show(img,'img')
masked_img = cv2.bitwise_and(img,img,mask=mask)#与操作
cv_show(masked_img,'masked_img')
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()
#直方图均衡化
img = cv2.imread('D:\\openCV files\\data\\2\\clahe.jpg',0)#0表示灰度图
plt.hist(img.ravel(),256) #ravel()多维降为一维,一共256个BIN
plt.show()
equ = cv2.equalizeHist(img) #更亮了,但丢失了不少细节
plt.hist(equ.ravel(),256)
plt.show()
res = np.hstack((img,equ))
cv_show(res,'res')
#自适应直方图均衡化
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')
out7:
(256, 1)