OpenCV2-图像基本操作-阈值与平滑处理-形态学-梯度运算
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 1. 图像基本操作
- 1.0 基本操作
- 1.1 视频的读取与处理
- 1.2 截取部分图像数据
- 1.3 颜色通道提取
- 1.4 边界填充
- 1.5 数值计算
- 1.6 图像融合
- 2. 阈值与平滑处理
- 2.1 图像阈值
- 2.2 图像平滑
- 2.3 高斯与中值滤波
- 3. 图像形态学操作
- 3.1 形态学-腐蚀操作
- 3.2 形态学-膨胀操作
- 3.3 开运算与闭运算
- 3.4 梯度运算
- 3.5 礼帽与黑帽
- 4. 图像梯度运算
- 4.1 图像梯度-Sobel算子
- 4.2 图像梯度-Scharr算子和laplacian算子
- 总结
前言
1. 图像基本操作
1.0 基本操作
import cv2 #opencv读取的格式是BGR
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inlineimg=cv2.imread('../cat.jpg')
%matplotlib inline 是一个特殊的 IPython 魔法命令
执行该命令后,后续所有 plt.plot()、plt.imshow() 等绘图命令的结果会直接显示在代码单元格的下方
图像会被保存为静态图片嵌入到笔记本中,方便保存和分享
不需要再额外调用 plt.show() 来显示图像(不过调用了也不会有问题)
#图像的显示,也可以创建多个窗口
cv2.imshow('image',img)
# 等待时间,毫秒级,0表示任意键终止
cv2.waitKey(10000)
cv2.destroyAllWindows()
过了十秒,弹窗就自动消失了
def cv_show(name,img):cv2.imshow(name,img) cv2.waitKey(0) cv2.destroyAllWindows()
创建了一个自动显示的函数
img=cv2.imread('../img/cat.jpg',cv2.IMREAD_GRAYSCALE)
img
这个是以灰度图的形式展示
#图像的显示,也可以创建多个窗口
cv2.imshow('image',img)
# 等待时间,毫秒级,0表示任意键终止
cv2.waitKey(10000)
cv2.destroyAllWindows()
#保存
cv2.imwrite('mycat.png',img)
type(img)
img.size
img.dtype
1.1 视频的读取与处理
视频也是由图片组成的
每一帧都是一个静止的图像,这样就可以了
每s30帧,就是一秒30张图—》我们正常人类就看不出来是卡的
每秒15帧,图片之间的间隔比较大–》比较卡,看着
把视频拆分为每一帧就可以操作了
vc = cv2.VideoCapture('../img/test.mp4')
# 检查是否打开正确
if vc.isOpened(): open, frame = vc.read()
else:open = False
vc.read()就是读取第一帧
然后就是第二帧,一直读取
这一帧读取成功了,那么oepn就是true
frame 就是这一帧的图像数据
vc.read() 是读取一帧图像的方法
返回两个值:
open(布尔值):表示是否成功读取到帧
frame(numpy 数组):读取到的图像帧数据(BGR 格式)
这行代码的作用是尝试从设备中读取第一帧图像
while open:ret, frame = vc.read()if frame is None:breakif ret == True:gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)cv2.imshow('result', gray)if cv2.waitKey(10) & 0xFF == 27:break
vc.release()
cv2.destroyAllWindows()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
将彩色帧(BGR 格式)转为灰度图
cv2.imshow(‘result’, gray)
在名为 result 的窗口中显示灰度图
if cv2.waitKey(10) & 0xFF == 27:
等待 10 毫秒,检测键盘输入
27 是 ASCII 码,对应键盘上的 Esc 键
break
如果按下 Esc 键,退出循环
vc.release()
释放视频捕获设备资源(如摄像头)
cv2.destroyAllWindows()
关闭所有 OpenCV 创建的窗口
循环读取视频帧 → 转为灰度图 → 显示图像 → 检测到 Esc 键或无有效帧时退出 → 释放资源
cv2.waitKey(10)
这是 OpenCV 的键盘输入等待函数。
参数 10 表示等待 10 毫秒(单位:毫秒)。在这段时间内,如果用户按下任何键,函数会返回该键的 ASCII 码;如果没有按键,返回 -1。
作用:给系统留出时间处理窗口事件(比如显示图像),同时检测键盘输入。
& 0xFF
这是一个位运算,用于提取按键值的低 8 位(即 0-255 的范围)。
因为 cv2.waitKey() 的返回值在不同系统上可能是 32 位整数(高 24 位可能包含其他信息),通过与 0xFF(二进制 11111111)做与运算,可以确保只保留有效的 ASCII 码部分。
== 27
27 是 ASCII 码中对应的 Esc 键(Escape 键)。
整个条件的意思是:如果用户在 10 毫秒内按下了 Esc 键,则条件成立。
每次都会在这里等10ms
这样就变成了灰度视频了
if cv2.waitKey(100) & 0xFF == 27:
如果是这样的话,就很慢了
1.2 截取部分图像数据
#截取部分图像数据
img=cv2.imread('../img/cat.jpg')
cat=img[0:200,0:200]
cv_show('cat',cat)
1.3 颜色通道提取
b,g,r=cv2.split(img)
img=cv2.merge((b,g,r))
img.shape
这样就可以1组合起来了
# 只保留R
cur_img = img.copy()
#bgr,所以b的索引为0
cur_img[:,:,0] = 0
cur_img[:,:,1] = 0
cv_show('R',cur_img)
这样就是只有R了
# 只保留G
cur_img = img.copy()
cur_img[:,:,0] = 0
cur_img[:,:,2] = 0
cv_show('G',cur_img)
1.4 边界填充
#上下左右分别填充的大小
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,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(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()
plt.subplot(231) 是 Matplotlib 中用于创建子图的函数,其参数 231 是一个三位数的缩写,具体含义如下:
第一位数字 2 表示行数:整个图像区域将被分成 2 行
第二位数字 3 表示列数:整个图像区域将被分成 3 列
第三位数字 1 表示位置索引:当前子图位于第 1 个位置
BORDER_REPLICATE:复制法,也就是复制最边缘像素。
BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制例如:fedcba|abcdefgh|hgfedcb
BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称,gfedcb|abcdefgh|gfedcba
BORDER_WRAP:外包装法cdefgh|abcdefgh|abcdefg
BORDER_CONSTANT:常量法,常数值填充。
第一个是原图
1.5 数值计算
img_cat=cv2.imread('../img/cat.jpg')
img_dog=cv2.imread('../img/dog.jpg')
img_cat2= img_cat +10
img_cat[:5,:,0]
加法就是在每一个位置上都加上10
#相当于% 256
(img_cat + img_cat2)[:5,:,0]
142+152=294
294%256=38
cv2.add(img_cat,img_cat2)[:5,:,0]
142+152=294~~255
1.6 图像融合
img_cat + img_dog
这样是不行的额,因为行列不一样
我们把它们变为一样的
img_dog = cv2.resize(img_dog, (500, 414))
img_dog.shape
res = cv2.resize(img, (0, 0), fx=3, fy=1)
plt.imshow(res)
(0, 0) 是 dsize 参数的值,表示不直接指定输出图像的尺寸,而是通过后面的缩放因子 fx 和 fy 来计算新尺寸
fx=3 表示水平方向放大 3 倍
fy=1 表示垂直方向保持原尺寸不变
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
这个表示按照权值相加
但是前提肯定是长宽相同
plt.imshow(res)
plt.show()
2. 阈值与平滑处理
2.1 图像阈值
ret, dst = cv2.threshold(src, thresh, maxval, type)
src: 输入图,只能输入单通道图像,通常来说为灰度图dst: 输出图thresh: 阈值maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值type:二值化操作的类型,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INVcv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0cv2.THRESH_BINARY_INV THRESH_BINARY的反转cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, 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()
THRESH_BINARY表示当前像素点大于127的话,那么就赋值为255,大于127,有点亮,255就超级亮–》白点
小于阈值就取0—》黑点,黑的全为黑了
thresh1 是图像的值,ret是阈值
THRESH_BINARY_INV 中INV表示反转
2.2 图像平滑
#%% md

#%%
img = cv2.imread('../img/lenaNoise.png')cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
有些白点—》去掉
我们这里要用到均值滤波,意思就是把204变为周围3*3的平均值,每个值都这样处理
怎么计算呢,我们可以把这个33的矩阵去乘以33的全1矩阵
# 均值滤波
# 简单的平均卷积操作
blur = cv2.blur(img, (3, 3))cv2.imshow('blur', blur)
cv2.waitKey(0)
cv2.destroyAllWindows()
发现白点没有那么明显了
# 方框滤波
# 基本和均值一样,可以选择归一化
box = cv2.boxFilter(img,-1,(3,3), normalize=True) cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()
参数 -1 表示输出图像的深度(即像素值的数据类型)与输入图像保持一致。
当 normalize=True 时,滤波核(方框)内所有像素的权重会被平均化(总和为 1)。计算方式是:将方框内所有像素值相加后,再除以方框内像素的总数量(即方框面积),这样可以保证输出像素值不会超出原图像的像素值范围。
如果设置 normalize=False,则不进行归一化,此时计算方式只是简单地将方框内所有像素值相加。这种情况下,如果方框较大,可能会导致像素值超过最大值(如 255),从而出现图像过曝(白块)现象。
所以得到的结果和均值滤波一样
# 方框滤波
# 基本和均值一样,可以选择归一化
box = cv2.boxFilter(img,-1,(3,3), normalize=False)cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()
False就没有除以9了,直接超级白了
2.3 高斯与中值滤波
高斯滤波就是这样的,近的就占的比重大点
# 高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
aussian = cv2.GaussianBlur(img, (5, 5), 1) cv2.imshow('aussian', aussian)
cv2.waitKey(0)
cv2.destroyAllWindows()
中值滤波就是3*3数字排完序之后,取中间的值
# 中值滤波
# 相当于用中值代替
median = cv2.medianBlur(img, 5) # 中值滤波cv2.imshow('median', median)
cv2.waitKey(0)
cv2.destroyAllWindows()
5表示5*5
基本上都不见了
因为噪音点少,而且噪音点比较亮,值比较大,所以排序选中间一般可能选不到它
但是取平均的话,很有可能就会偏亮,因为噪音点值大
# 展示所有的
res = np.hstack((blur,aussian,median))
print (res)
cv2.imshow('median vs average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
这个就是图像拼接,hstack表示横着拼接
vstack表示竖着拼接
3. 图像形态学操作
3.1 形态学-腐蚀操作
img = cv2.imread('../img/dige.png')cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这就是图像操作的前提,图像数据是二值的
意思就是只有两种颜色
怎么去掉那些虚线呢–》腐蚀操作
pie = cv2.imread('../img/pie.png')cv2.imshow('pie', pie)
cv2.waitKey(0)
cv2.destroyAllWindows()
腐蚀操作意思就是当一个点的3*3范围内的时候,有两种颜色----》这个点就腐蚀掉-----》置于黑色
----》就会变小了,这个圆
腐蚀而且还有迭代次数的,每次腐蚀,圆都会变小
kernel = np.ones((3,3),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
kernel = np.ones((3,3),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 3)cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
(3,3)这个区间越大,或者迭代次数越多,那么腐蚀的力度就越大
kernel = np.ones((30,30),np.uint8)
erosion_1 = cv2.erode(pie,kernel,iterations = 1)
erosion_2 = cv2.erode(pie,kernel,iterations = 2)
erosion_3 = cv2.erode(pie,kernel,iterations = 3)
res = np.hstack((erosion_1,erosion_2,erosion_3))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.2 形态学-膨胀操作
这个就是变大了,和腐蚀是相反的效果
kernel = np.ones((3,3),np.uint8)
dige_erosion = cv2.erode(img,kernel,iterations = 1)cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
怎么变胖一点呢
kernel = np.ones((3,3),np.uint8)
dige_dilate = cv2.dilate(dige_erosion,kernel,iterations = 1)cv2.imshow('dilate', dige_dilate)
cv2.waitKey(0)
cv2.destroyAllWindows()
就是3*3的时候,有两种颜色的时候,变成白色
pie = cv2.imread('../img/pie.png')kernel = np.ones((30,30),np.uint8)
dilate_1 = cv2.dilate(pie,kernel,iterations = 1)
dilate_2 = cv2.dilate(pie,kernel,iterations = 2)
dilate_3 = cv2.dilate(pie,kernel,iterations = 3)
res = np.hstack((dilate_1,dilate_2,dilate_3))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.3 开运算与闭运算
# 开:先腐蚀,再膨胀
img = cv2.imread('../img/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('../img/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()
所以不能去掉毛刺
3.4 梯度运算
# 梯度=膨胀-腐蚀
pie = cv2.imread('../img/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)cv2.imshow('gradient', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.5 礼帽与黑帽
礼帽 = 原始输入-开运算结果(先腐蚀在膨胀)----》只剩下毛刺了
黑帽 = 闭运算(先膨胀在腐蚀)-原始输入 -----》原始的小轮廓了
#礼帽
img = cv2.imread('../img/dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
cv2.waitKey(0)
cv2.destroyAllWindows()
#黑帽
img = cv2.imread('../img/dige.png')
blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat ', blackhat )
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 图像梯度运算
4.1 图像梯度-Sobel算子
img = cv2.imread('../img/pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
什么是梯度呢,梯度就是一个点上,有不同颜色,就是有梯度—》横着竖着,分别看有没有梯度
假设我们选的点是p5,然后
咋们的这个矩阵计算呢,就是对应位置相乘的计算,并不是正在的矩阵乘法
所以这个梯度就是右边的值减去左边的,而且近的点,比重大,*2
这样就对比出来了左边与右边的差异—》也就是左右的梯度Gx
竖着的梯度就是下边的减去上边的
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)ddepth:图像的深度,一般是-1就可以了,输出的深度和输入深度一样
dx和dy分别表示水平和竖直方向
ksize是Sobel算子的大小
如果Gx为负数那么就取0,因为这里的颜色数值都是大于等于0的,而且只有32位,只能表示0~255
def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows()
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)cv_show(sobelx,'sobelx')
cv2.CV_64F,使用 64 位浮点型是为了能够表示负数值(
这样梯度为负数的时候,也是直接表示为负数了,不会表示为0了
dx=1,dy-0表示只算水平的梯度,不算竖直的梯度
因为算的是水平的,是右边减去左边,那么偏左的时候,白色-黑色是大于0,而且是白色,值不变,因为黑色是0
偏右的时候,黑色减去左边小于0,为-255,负数不能显示出来—》截断–》黑色
白到黑是正数,黑到白就是负数了,所有的负数会被截断成0,所以要取绝对值
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
cv_show(sobelx,'sobelx')
convertScaleAbs这个就是取绝对值了,因为CV_64F,所以里面存的是负数,再取绝对值,就对了
这个就是总的梯度计算
或者Gx的绝对值+Gy的绝对值
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
cv_show(sobely,'sobely')
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
就是0.5Gx+0.5Gy了
sobelxy=cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show(sobelxy,'sobelxy')
一开始的时候,就一起计算呢
所以说这就是为什么东南西北四个方向都没有白点的原因
不建议直接计算
img = cv2.imread('../img/lena.jpg',cv2.IMREAD_GRAYSCALE)
cv_show(img,'img')
这是一个灰度图
img = cv2.imread('../img/lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
就是找出轮廓的作用了
# 不建议直接计算img = cv2.imread('../img/lena.jpg',cv2.IMREAD_GRAYSCALE)sobelxy=cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show(sobelxy,'sobelxy')
我们发现直接计算就效果不好了,直接计算会少很多的点
4.2 图像梯度-Scharr算子和laplacian算子
发现这个差值更大了,但还是右边减去左边,下边减去上边
这个是中间点和边缘点的差值
如果这个点不在边界-----》变黑
在边界-------》有变化了
#不同算子的差异
img = cv2.imread('../img/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')
这个就是上面讲的三个算法
第二个描述得更细节了,你看那木头的纹路都弄出来了
Laplacian描述得一般