前言#

如题所示,记录了本人学习opencv时的具体代码与注释,包括一些常见问题,但可能不会太详细,因为是回顾,所以问题大多解决,后期欢迎评论区提问。桀桀桀!基于pycharm的opencv入门笔记-CSDN博客

如果你用下面的代码报错了,可能是一些库没添加,你可以根据报错提示来下载对应的库。、

显示图片及属性(点灯!)#

首先,选取一个自己喜欢的图片,放入当前项目的文件地址。以下是我选取的图片,以及它的文件名luna.jpg。

img

import cv2  
import matplotlib.pyplot as plt
import numpy as np

简单说,下面两行代码是为了方便后续的作图,引用的相关库。

plt.switch_backend('TkAgg')       #matplotlib的其中一个backend的选项

显示图片#

为了方便后续绘制的图片或图像方便缩放或者移动,使用上述代码。

import cv2 #opencv读取的格式是BGR
import matplotlib.pyplot as plt
import numpy as np
plt.switch_backend('TkAgg')#matplotlib的其中一个backend的选项

img=cv2.imread('luna.jpg')#在当前路径下读取图片
print(img) #打印像素数组

def cv_showimg(name,img):
    #图像显示,也可以创建多个窗口
    cv2.imshow(name,img)
    #等待时间,毫秒级,0表示任意键终止
    cv2.waitKey(0)
    cv2.destroyAllWindows()

cv_showimg('image',img)

运行得到一个名为img的窗口,显示图片。(下文的cv_showimg皆为上述代码中的定义函数)

imgimg

属性#

打印出图片的像素点,即像素色块的方阵序列。

print(img.shape) #读取图片的属性

结果为img

打印图片的像素高度,宽度以及颜色通道,图像的高度为 959像素,宽度为 959像素,并且有 3 个颜色通道(红、绿、蓝)。

#cv2.IMREAD_COLOR:彩色图像
#cv2.IMREAD_GRAYSCALE:灰度图像

图像可以由彩色,也能由灰度的形式表现。

img=cv2.imread('luna.jpg',cv2.IMREAD_GRAYSCALE)#将图片灰度化
cv_showimg('image',img)
cv2.imwrite('myluna.jpg',img)#保存图片

结果img

视频的读取与处理#

与显示图片之前一样,先将视频boat.mp4(我的)添加至文件地址。

vc=cv2.VideoCapture('boat.mp4')#在当前路径下读取视频
if vc.isOpened():#检查是否打开正确
    open, frame = vc.read()#frame是图像数组,即一帧图像
else:
    open = False
while open:#遍历视频中的每一帧
    ret, frame = vc.read()
    if frame is None:
        break
    if ret == True:
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)#转换为灰度图
        cv2.imshow('result',gray)
        if cv2.waitKey(10) & 0xFF == 27:#27为退出键
            break
vc.release()
cv2.destroyAllWindows

结果是,将视频的每一帧提取出来,进行灰度处理,再播放由处理后的灰度帧组成的视频,演示视频不方便放,可以自己试试。

代码 if cv2.waitKey(10) & 0xFF == 27:#27为退出键 即视频播放过程中按下ESC键,视频退出播放,可以将27改为其他数字,则对应的退出按钮也会改变。

截取部分图像数据(ROI)#

相当于裁剪图片大小,根据上文图片属性可知luna.jpg的高度为 959像素,宽度为 959像素。

img=cv2.imread('luna.jpg')
luna=img[300:800,100:500]
cv_showimg('luna',luna)

截取高度为300-800,宽度为100-500,结果为

img

窗口名称为luna

颜色通道提取#

img=cv2.imread('luna.jpg')#在当前路径下读取图片

b,g,r=cv2.split(img)#分离颜色通道,blue,green,red
img=cv2.merge((b,g,r))#重组

#只保留R,   B:0,G:1,R:2
cur_img=img.copy()#创建原始图像的副本
cur_img[:,:,0] = 0#把B设置成0
cur_img[:,:,1] = 0#把G设置成0
cv_showimg('R',cur_img)#最后只能保留2通道red

举个例子,先分离颜色通道,再重新组装成图像(实际没变)。复制原先的图像,并命名为cur_img,通过操作将副本的blue,green通道关闭(B:0,G:1,R:2),保留了red。

结果

img

边界填充#

边界填充就是给图片的边界续上一段,补上一段的感觉。常见的边界填充分为五种,

1.复制,在边界补上和原先一样的边界

2.反射,在两边进行复制

3.反射,以最边缘为轴,作对称,补上和原先一样的边界

4.外包装,上下边界一样复制,将原图包装或者镶嵌进去

5.常量,使用常数值填充,value=0表示边框填充为黑色,RGB值为 (0, 0, 0),也可以写作value=[0, 0, 0]

top_size, bottom_size, left_size, right_size = (50,50,50,50)#上下左右
#Type:复制,反射(在两边进行复制),反射(以最边缘位轴,对称),外包装法,常量法(常数值填充)
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, cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_CONSTANT,value=0)

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(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(replicate,'gray'),plt.title('REFLECT')
plt.subplot(235),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')

plt.show()

img点击并拖拽以移动

数值计算#

img_luna=cv2.imread('luna.jpg')
img_luna2=img_luna +10
img_luna[:5,:,0]
img_luna2[:5,:,0]
print((img_luna+img_luna2)[:5,:,0])#超过255,用这个数取余255
print(cv2.add(img_luna,img_luna2)[:5,:,0])#超过255,就是255

先解释下img_luna[:5,:,0],在 OpenCV 中,通常它的形状是行数,列数,还有通道数,:5 表示前五行,: 则是全部列,0是第0个通道,在 BGR 图像中,0是用来选择 BGR 中的蓝色通道。

将img_luna中的每个像素加10,cv2.add函数会进行饱和相加,如果某个像素值超过 255,它会被设置为 255,数值计算后面会有用。

结果img点击并拖拽以移动

图像融合#

import matplotlib.pyplot as plt
import numpy as np
plt.switch_backend('TkAgg')#matplotlib的其中一个backend的选项
img_luna=cv2.imread('luna.jpg')
img_lovegood=cv2.imread('lovegood.jpg')
print(img_luna.shape)
img_lovegood=cv2.resize(img_lovegood,(959, 959))
print(img_lovegood.shape)
res = cv2.addWeighted(img_luna, 0.4, img_lovegood, 0.6, 10)
plt.imshow(res)
plt.show()#使用matplotlib时最后一定要加

将两张图片进行融合,每张图片的尺寸维度和混合权重可以设置。

结果img点击并拖拽以移动

图片阈值#

阈值可以理解为图片的像素值,比如说亮度。凡事先将图片灰度化[捂脸],通常有5个方法。

第一个,超过阈值(比如127)了,那就是255,反之则为0(亮为白点,暗为黑点)

第二个,未超过阈值(比如127)了,那就是255,反之则为0(亮为黑点,暗为白点)

第三个,截断,超过阈值即为阈值,未超过阈值就是本身

第四个,大于阈值为本身,小于或者等于阈值即为0

第五个,小于阈值为本身,其余为0

import matplotlib.pyplot as plt
import numpy as np
plt.switch_backend('TkAgg')#matplotlib的其中一个backend的选项
img_lovegood=cv2.imread('lovegood.jpg')
img_lovegood_gray=cv2.imread('lovegood.jpg',cv2.IMREAD_GRAYSCALE)#将图片灰度化
ret, thresh1 = cv2.threshold(img_lovegood_gray, 127, 255, cv2.THRESH_BINARY)#超过阈值(127)即为255,反之则为0(亮为白点,暗为黑点)
ret, thresh2 = cv2.threshold(img_lovegood_gray, 127, 255, cv2.THRESH_BINARY_INV)#上面反转,超过阈值(127)为0,反之则为255(亮为黑点,暗为白点)
ret, thresh3 = cv2.threshold(img_lovegood_gray, 127, 255, cv2.THRESH_TRUNC)#截断值,大于阈值即为阈值(大于127=127),小于阈值不变为本身
ret, thresh4 = cv2.threshold(img_lovegood_gray, 127, 255, cv2.THRESH_TOZERO)#大于阈值不变为本身,其余为0
ret, thresh5 = cv2.threshold(img_lovegood_gray, 127, 255, cv2.THRESH_TOZERO_INV)#上面反转,小于阈值不变为本身,其余为0

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img_lovegood, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2,3, i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

结果img点击并拖拽以移动

滤波(图像平滑处理)#

原图img点击并拖拽以移动

简单来说,滤波就相当于磨皮。

均值滤波#

img_charming_girl=cv2.imread('table luna.jpg')
cv_showimg('img', img_charming_girl)
smaller = cv2.resize(img_charming_girl, (0,0), fx=0.6, fy=0.6)#把图片缩小

blur = cv2.blur(smaller, (3,3))
cv_showimg('blur', blur)

因为原图较大,先将原图缩小,然后使用均值滤波。

方框滤波#

box = cv2.boxFilter(smaller, -1, (3,3), normalize=True)#or False

高斯和中值滤波#

#高斯滤波
aussian = cv2.GaussianBlur(smaller, (5,5), 1)
#中值滤波(good)
median = cv2.medianBlur(smaller, 5)

高斯滤波与卷积核的计算相关,较小的值会保留更多细节,大一些会模糊化,原理不深入探讨。个人感觉中值滤波更好,下面附上所有滤波的对比。

img点击并拖拽以移动

第一张是原图,后面依次。

形态学操作#

腐蚀(去毛刺)#

通过缩小图像中的物体(通常是白色区域)去掉不需要的细节,比如小的突出部分。

img点击并拖拽以移动

最典型的一张图片,经过三次腐蚀得到img点击并拖拽以移动

每次腐蚀的多少也是根据所选卷积核的大小得到的,这里不过多阐述。

img_fushi = cv2.imread('fushi3.png')
smaller = cv2.resize(img_fushi, (0,0), fx=0.4, fy=0.4)#把图片缩小
# cv_showimg('img', res)
kernel = np.ones((5,5), np.uint8)#选择核的矩阵
erosion_1 = cv2.erode(smaller, kernel, iterations = 1)
erosion_2 = cv2.erode(smaller, kernel, iterations = 2)
erosion_3 = cv2.erode(smaller, kernel, iterations = 3)
res_erosion = np.hstack((smaller,erosion_1, erosion_2, erosion_3))###np.vstack也行
cv_showimg('res_erosion', res_erosion)

而将(5,5)改为(8,8)后,差距显而易见。img点击并拖拽以移动

膨胀#

腐蚀后可进行膨胀操作,将以(5,5)卷积核腐蚀后的结果以(3,3)膨胀三次。

kernel = np.ones((3,3), np.uint8)
dilate_1 = cv2.dilate(erosion_1, kernel, iterations = 1)#把腐蚀后的结果传入进行膨胀
dilate_2 = cv2.dilate(erosion_1, kernel, iterations = 2)
dilate_3 = cv2.dilate(erosion_1, kernel, iterations = 3)
res_dilate = np.hstack((dilate_1, dilate_2, dilate_3))###np.vstack也行
cv_showimg('res_dilate', res_dilate)

img点击并拖拽以移动

开运算#

开运算就是先腐蚀再膨胀,下图为例。

img
img = cv2.imread('fushi2.png')
smaller = cv2.resize(img, (0,0), fx=0.4, fy=0.4)#把图片缩小

kernel = np.ones((5,5), np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

cv_showimg('opening', opening)

结果img点击并拖拽以移动

效果还不错。

闭运算#

img = cv2.imread('fushi2.png')
smaller = cv2.resize(img, (0,0), fx=0.4, fy=0.4)#把图片缩小

kernel = np.ones((5,5), np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

cv_showimg('closing', closing)

结果img点击并拖拽以移动

似乎没什么效果,没关系,我们试一下(8,8)

结果img点击并拖拽以移动

[捂脸][捂脸][捂脸]

梯度#

梯度=膨胀-腐蚀

img = cv2.imread('fushi2.png')
kernel = np.ones((5,5), np.uint8)#选择5*5矩阵的核
dilate = cv2.dilate(img, kernel, iterations = 1)#膨胀1次
erosion = cv2.erode(img, kernel, iterations = 1)#腐蚀1次

res = np.hstack((dilate, erosion))
cv_showimg('res', res)

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)#进行梯度运算,膨胀-腐蚀
cv_showimg('gradient', gradient)

先展示膨胀和腐蚀的结果

img点击并拖拽以移动

进行梯度运算后

img点击并拖拽以移动

礼帽与黑帽#

礼帽=原始输入-开运算结果

#礼帽
img = cv2.imread('fushi2.png')
kernel = np.ones((5,5), np.uint8)#选择5*5矩阵的核
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)#礼帽
cv_showimg('tophat', tophat)

结果img点击并拖拽以移动

黑帽=闭运算-原始输入

##黑帽
img = cv2.imread('fushi2.png')
kernel = np.ones((5,5), np.uint8)#选择5*5矩阵的核
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)#黑帽
cv_showimg('blackhat', blackhat)

结果img点击并拖拽以移动

可以发现结果就是两次运算与原图的相减。

轮廓#

图像轮廓#

mode:轮廓检索模式 cv2.findContours(img, mode, method) RETR_EXTERNAL:只检索最外面的轮廓 RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中 RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外界边界,第二层是空洞的边界 RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次(最常用的)

method:轮廓逼近方法 CHAIN_APPROX_NONE: 以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)(长方形四边) CHAIN_APPROX_SIMPLE:压缩水平的,垂直的和斜的部分,也就是,函数只保留他们的终点部分(长方形四顶点)

####为更高的准确度,使用二值法
gray = cv2.imread('fushi2.png',cv2.IMREAD_GRAYSCALE)#将图片灰度化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# cv_showimg('thresh', thresh)

##contours是一堆轮廓点(轮廓信息),hierarchy是层级
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)##由于版本问题,必须删除一个参数
############绘制轮廓
#传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
#需要copy,不然原图会变
img = cv2.imread('fushi2.png')
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)##(0,0,255)是BGR,-1是整体轮廓,2是线条的宽度
cv_showimg('res', res)

img点击并拖拽以移动

轮廓特征与近似#

轮廓特征包括面积和周长,可以采用

cnt = contours[0]
#面积
contourArea = cv2.contourArea(cnt)
print(contourArea)
#周长,True表示闭合的
arcLength = cv2.arcLength(cnt, True)
print(arcLength)

得到轮廓特征。

以下图为例,

img点击并拖拽以移动

img = cv2.imread('jinsi.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)##(0,0,255)是BGR,-1是整体轮廓,2是线条的宽度
cv_showimg('res', res)

先使用二值法进行图像处理,如果图中只有一个轮廓,就直接用数组的第一个元素,用红色描出轮廓,设置宽度。

img点击并拖拽以移动

轮廓近似法#

espilon = 0.01 * cv2.arcLength(cnt, True)##0.1倍周长
approx = cv2.approxPolyDP(cnt, espilon, True)##轮廓近似的数组

draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)##(0,0,255)是BGR,-1是整体轮廓,2是线条的宽度
cv_showimg('res', res)

点击并拖拽以移动

img点击并拖拽以移动

边界矩形#

img = cv2.imread('jinsi.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]

x,y,w,h = cv2.boundingRect(cnt)#得到边界矩形
img = cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)#做出矩形框,(0,255,0)绿色
cv_showimg('img', img)

area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)#边界矩形

img点击并拖拽以移动

由此还可以作外接圆

(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_showimg('img', img)

img点击并拖拽以移动#

图像梯度#

Sobel算子#

例一#

img点击并拖拽以移动

以上图为例。

先介绍下相关函数

dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
#ddepth: 图像的深度(通常为-1,表示输出和输入深度是一样的),(cv2.CV_64F表示能使用负数)
#dx和dy分别表示水平和竖直方向
#ksize是Sobel算子的大小(几乘几的矩阵大小)

先使dx=1,dy=0(算水平不算竖直)(没取绝对值,只有白到黑那一半),

img = cv2.imread('pi.png')
# cv_showimg('img', img)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)#dx=1,dy=0(算水平不算竖直)(没取绝对值,只有白到黑那一半)
cv_showimg('sobelx', sobelx)

结果img点击并拖拽以移动

上述代码中未加绝对值,白到黑是正数,黑到白是负数,所有负数会被截断成0,所以要取绝对值。

sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)#dx=1,dy=0(算水平不算竖直)(先把正负数都算出来)
sobelx = cv2.convertScaleAbs(sobelx)#Abs是算负数的绝对值

结果img点击并拖拽以移动

再将x,y相掉,

sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize = 3)#dx=0,dy=1(算竖直不算水平)
sobely = cv2.convertScaleAbs(sobely)#Abs是算负数的绝对值
cv_showimg('sobely', sobely)

得到img点击并拖拽以移动

最后将x,y相结合,得到xy梯度

sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_showimg('sobelxy', sobelxy)

得到img点击并拖拽以移动

然后就有人要问了,为什么不直接计算xy梯度,而要分别计算出x梯度和y梯度然后相结合才得出xy梯度呢?我们看下直接计算xy梯度的效果。

#直接计算xy梯度(不建议)
sobelxy = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize = 3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_showimg('sobelxy', sobelxy)

结果img点击并拖拽以移动 ,得到的差异还是挺大的,四周边框消失,白点更多,更密集。

例二#

img = cv2.imread('luna.jpg',cv2.IMREAD_GRAYSCALE)
# cv_showimg('img', img)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)#dx=1,dy=0(算水平不算竖直)(先把正负数都算出来)
sobelx = cv2.convertScaleAbs(sobelx)#Abs是算负数的绝对值
# cv_showimg('sobelx', sobelx)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize = 3)#dx=0,dy=1(算竖直不算水平)
sobely = cv2.convertScaleAbs(sobely)#Abs是算负数的绝对值
# cv_showimg('sobely', sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_showimg('sobelxy', sobelxy)
###整体进行计算(不建议)
sobelxy = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize = 3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_showimg('sobelxy', sobelxy)

x梯度结果img点击并拖拽以移动

y梯度结果img点击并拖拽以移动

xy梯度(x梯度和y梯度相结合)

img点击并拖拽以移动

直接计算xy梯度

img点击并拖拽以移动

对比就出来了。

几种算子的对比#

除了Sobel算子外,还有Scharr算子,laplacian算子,不在一一阐述。

img = cv2.imread('loveluna.jpg',cv2.IMREAD_GRAYSCALE)
smaller = cv2.resize(img, (0,0), fx=0.25, fy=0.25)#把图片缩小

sobelx = cv2.Sobel(smaller, cv2.CV_64F, 1, 0, ksize = 3)#dx=1,dy=0(算水平不算竖直)(先把正负数都算出来)
sobelx = cv2.convertScaleAbs(sobelx)#Abs是算负数的绝对值
sobely = cv2.Sobel(smaller, cv2.CV_64F, 0, 1, ksize = 3)#dx=0,dy=1(算竖直不算水平)
sobely = cv2.convertScaleAbs(sobely)#Abs是算负数的绝对值
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)

scharrx = cv2.Scharr(smaller, cv2.CV_64F, 1, 0)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.Scharr(smaller, cv2.CV_64F, 0, 1)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)

laplacian = cv2.Laplacian(smaller, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

res = np.hstack((smaller, sobelxy, scharrxy, laplacian))
cv_showimg('res', res)

对比结果img点击并拖拽以移动

边缘检测#

这里使用Canny边缘检测,

1)使用高斯滤波器,以平滑图像,滤除噪声 2)计算图像中每个像素点的梯度强度和方向 3)应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散相应 4)应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘 5)通过抑制孤立的弱边缘最终完成边缘检测

以一张小猫图片为例,上来先缩图[捂脸][捂脸][捂脸]

img点击并拖拽以移动

img = cv2.imread('cat.jpg',cv2.IMREAD_GRAYSCALE)
smaller = cv2.resize(img, (0,0), fx=0.3, fy=0.3)#把图片缩小

v1 = cv2.Canny(smaller, 120, 250)
v2 = cv2.Canny(smaller, 50, 100)

res= np.hstack((smaller, v1, v2))
cv_showimg('res', res)

结果img点击并拖拽以移动

金字塔#

图像金字塔#

对图像,先上采样,再下采样,即先放大再缩小,最终得到的图片是较模糊的。

####################图像金字塔,金字塔制作方法
img = cv2.imread('loveluna.jpg')
smaller = cv2.resize(img, (0,0), fx=0.3, fy=0.3)#把图片缩小
# cv_showimg('img', img)
print(smaller.shape)

up = cv2.pyrUp(smaller)##上采样(放大)
# cv_showimg('up', up)
print(up.shape)

down = cv2.pyrDown(smaller)##下采样(缩小)
# cv_showimg('down', down)
print(down.shape)

up_down = cv2.pyrDown(up)##先向上采样后向下采样,得到的图像比原来模糊
# cv_showimg('up_down', up_down)
print(up_down.shape)

res = np.hstack((smaller, up_down))
cv_showimg('res', res)

结果img点击并拖拽以移动

img点击并拖拽以移动

拉普拉斯金字塔#

拉普拉斯金字塔 先低通滤波,然后缩小尺寸,放大尺寸,图像相减。。

img = cv2.imread('loveluna.jpg')
smaller = cv2.resize(img, (0,0), fx=0.5, fy=0.5)#把图片缩小
# cv_showimg('img', img)
down = cv2.pyrDown(smaller)
down_up = cv2.pyrUp(down)
i_1 = smaller - down_up
cv_showimg('i_1', i_1)

结果img点击并拖拽以移动

有点感人。

模板匹配#

简单来说,就是使用对比检测的方法判断某局部图像在整体图像中的位置。

TM_SQDIFF:计算平方不同,计算出的值越小,越相关 TM_CCORR:计算相关性,计算出的值越大,越相关 TM_CCOEFF:计算相关系数,计算出的值越大,越相关 TM_SQDIFF_NORMED:计算归一化平方不同,计算出的值越接近0,越相关 TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关 TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关

接下来一一使用

img点击并拖拽以移动 为整体图片

img点击并拖拽以移动 为局部图片

img = cv2.imread('charming girl.jpg', 0)
template = cv2.imread('girl face.png', 0)
h, w = template.shape[:2]
print("img.shape is",img.shape)
print("template.shape is",template.shape)

methods = ['cv2.TM_CCOEFF','cv2.TM_CCORR_NORMED','cv2.TM_CCORR',
           'cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED']
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
print("res.shape is",res.shape)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print("min_val is",min_val)
print("max_val is",max_val)
print("min_loc is",min_loc)##左上角的点
print("max_loc is",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点击并拖拽以移动 img点击并拖拽以移动 img点击并拖拽以移动 img点击并拖拽以移动 img点击并拖拽以移动 img点击并拖拽以移动

直方图#

绘制#

cv2.calcHist(images, channels, mask, histSize, ranges) images:原图像格式为unit8或float32,传入函数时用[]先把正负数都算出来 channels:用[],如果入图像是灰度图,它的值就是[0],如果是彩色图像的传入的参数可以是[0][1][2]它们对应BGR mask:掩模图像,统整幅图像的直方图就把它为None,但是如果想统图像某一分的直方图,就制作一个掩模图像并使用它 histSize: BIN的数目。也应用[],直方图每个范围大小 range: 像素值范围常为[0, 256]

img = cv2.imread('lovegood.jpg')
hist = cv2.calcHist([img], [0], None, [256], [0,256])
# print("hist.shape is",hist.shape)
plt.hist(img.ravel(), 256)
plt.show()
#####遍历三种颜色的直方图
img = cv2.imread('many cats.jfif')
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])
plt.show()

遍历三种颜色的直方图

img点击并拖拽以移动

掩码(mask)#

掩码操作是为了截取图像的一部分,先创建掩码,掩码的像素数组必须小于待截取图像的大小,接着截取图像

img点击并拖拽以移动 img点击并拖拽以移动

最后列出进行对比,并绘制该区域直方图,

img点击并拖拽以移动

#mask操作(掩码)##截取图像操作
#创建mask
img = cv2.imread('lovegood.jpg')
print(img.shape)
mask = np.zeros(img.shape[:2], np.uint8)#无符号整形
mask[200:450, 200:450] = 255
cv_showimg('mask', mask)

img = cv2.imread('lovegood.jpg', 0)
# cv_showimg('img', img)
masked_img = cv2.bitwise_and(img, img, mask=mask)#与操作
cv_showimg('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('lovegood.jpg', 0)
smaller = cv2.resize(img, (0,0), fx=0.6, fy=0.6)#把图片缩小
plt.hist(smaller.ravel(), 256)
# plt.show()

equ = cv2.equalizeHist(smaller)#直方图均衡化
plt.hist(equ.ravel(), 256)
# plt.show()

res = np.hstack((smaller, equ))
cv_showimg("res", res)
#自适应均衡化
clahe = cv2.createCLAHE(clipLimit = 2.0, tileGridSize = (8, 8))
res_clahe = clahe.apply(smaller)
res = np.hstack((smaller, equ, res_clahe))
cv_showimg('res', res)

img点击并拖拽以移动

傅里叶变换#

这节了解下就行。

高频:变化剧烈的灰度分量

低频:变化缓慢的灰度分量

滤波:低通:只保留低频,使图像模糊

高通:只保留高频,使图像细节增强

img = cv2.imread('charming girl.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]))##将实部和虚部进行处理
#低频在中间的表达
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()
####低通滤波
img = cv2.imread('charming girl.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##左30,右30,上下30分别置为1(除了中间是1,其余都是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()

####################高通滤波
img = cv2.imread('charming girl.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##左30,右30,上下30分别置为1(除了中间是0,其余都是1)
#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()

低通,高通滤波结果

img点击并拖拽以移动img点击并拖拽以移动

最后#

加油各位,that is all,thank you!