OpenCV-Python笔记(上)

news2024/12/22 10:30:48

安装

全局安装

pip install opencv-python

项目虚拟环境安装

# 进入项目根路径执行
.venv/bin/pip install opencv-python

计算机眼中的图像

一张图片由大小比如(100*100)决定,说明存在100*100的像素点,每个像素点存在颜色通道,我们所看到的彩色均由RGB(Red红色、、Green绿色、Blue蓝色)三原色组成,不同的颜色组合在一起就会在视觉上看到新的颜色,比如红色+绿色,看到的就是黄色

在这里插入图片描述

因为RGB三通道模式,对于彩色图片就存在 2 3 2^3 23即八种标准颜色(只考虑0或者255)。

  • 纯红色:RGB(255, 0, 0)
  • 纯绿色:RGB(0, 255, 0)
  • 纯蓝色:RGB(0, 0, 255)
  • 黄色(红色+绿色):RGB(255, 255, 0)
  • 青色(绿色+蓝色):RGB(0, 255, 255)
  • 品红色(红色+蓝色):RGB(255, 0, 255)
  • 白色:RGB(255, 255, 255)
  • 黑色:RGB(0, 0, 0)

在计算机中我们用[r,g,b]这样一个定长的一维数组表示一个像素点,其中元素顺序可变,所以存在RGB,BGR模式的说法。于是对于一张图片的表示如下:

图片矩阵 [ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ⋯ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ r , g , b ] ⋯ [ r , g , b ] [ 255 , 0 , 0 ] ⋮ ⋮ ⋮ ⋮ [ 255 , 0 , 0 ] [ r , g , b ] ⋯ [ r , g , b ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ⋯ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ] 图片矩阵 \left[ \begin{matrix} [255,0,0] & [255,0,0] & \cdots & [255,0,0] & [255,0,0]\\ [255,0,0] & [r,g,b] & \cdots & [r,g,b] & [255,0,0]\\ \vdots & \vdots & & \vdots& \vdots && \\ [255,0,0] & [r,g,b] & \cdots & [r,g,b] & [255,0,0]\\ [255,0,0] & [255,0,0] & \cdots & [255,0,0] & [255,0,0] \end{matrix} \right] 图片矩阵 [255,0,0][255,0,0][255,0,0][255,0,0][255,0,0][r,g,b][r,g,b][255,0,0][255,0,0][r,g,b][r,g,b][255,0,0][255,0,0][255,0,0][255,0,0][255,0,0]

我们用[r,g,b]表示一个像素点,于是图片矩阵中每一行的像素点表示为:

red_row1 = [[r1,g1,b1],...,[rn,gn,bn]]

上面这样的行存在多少个呢?这就是列,也就是图片高度height的像素点个数。

red_img = [[[r1,g1,b1],...,[rn,gn,bn]]
,
		  [[r1,g1,b1],...,[rn,gn,bn]]
,...,
		  [[r1,g1,b1],...,[rn,gn,bn]]
]

最后图片在计算机看到的数据就是这样:
在这里插入图片描述

如果我们只查看几个像素点数据,可以这样:

img = cv2.imread("img.png")  
# 获取高度两个像素点,宽度3个像素点,即共6个像素点展示  
print(img[:2, :3:])  
print("="*20)  
# 获取高度一个像素点,宽度三个像素点,B通道的数据  
b = img[:1, :3, 0]  
# 获取高度一个像素点,宽度三个像素点,G通道的数据  
g = img[:1, :3, 1]  
# 获取高度一个像素点,宽度三个像素点,R通道的数据  
r = img[:1, :3, 2]  
print(b)  
print(g)  
print(r)

输出
在这里插入图片描述

总结: 计算机中的图片由矩阵像素点(pixel) 表示,其中每一个像素点由一维数组[b,g,r]定长一维数组表示,不同的数值代表不同的颜色,按照三原色通道,彩色图存在三个通道的二维数组

Note:

  • 通道顺序是可变的,不同的排列意味着模式不同[b,g,r]表示BGR模式[r,b,g]表示RBG模式。
  • 对于灰度图,因为不需要三个通道表示,因此一个数值就表示一个像素点,所以灰度图单纯是一个二维矩阵描述图片像素点。
  • OpenCV读取图片默认是BGR模式

ROI

ROI(Region Of Interest) 感兴趣的区域,可用于截取特定区域图或者特定通道图。

截取特定区域

# 读取图片为三维数组数据
img = cv2.imread("person.jpg")  
# 截取高度500像素,宽度1000像素
img = img[0:500, 0:1000]  

# 查看图片,按q退出
cv2.imshow('person', img)  
if cv2.waitKey() & 0xFF == 'q':  
    cv2.destroyAllWindows()

效果图
在这里插入图片描述

颜色通道提取

img = cv2.imread("person.jpg")  
# 返回不同通道的数据,是一个二维数组!!!  
b, g, r = cv2.split(img)  
  
# 只保留b通道数据的图片  
blue_img = img.copy()  
blue_img[:, :, 1] = 0  
blue_img[:, :, 2] = 0  
  
# 只保留g通道数据的图片  
green_img = img.copy()  
green_img[:, :, 0] = 0  
green_img[:, :, 2] = 0  
  
# 只保留r通道数据的图片  
red_img = img.copy()  
red_img[:, :, 0] = 0  
red_img[:, :, 1] = 0

效果图
在这里插入图片描述

图片融合

# cv2打开图片默认为BGR格式,需要转为RGB格式  
target = cv2.cvtColor(cv2.imread(img1), cv2.COLOR_BGR2RGB)  
height, width, _ = target.shape  
cv2_img2 = cv2.cvtColor(cv2.imread(img2), cv2.COLOR_BGR2RGB)  
  
# 两张图片融合需要保证宽高一致,因此需要重新调整大小  
background = cv2.resize(cv2_img2, (width, height))  
  
# 合并图片  
merged = cv2.addWeighted(target, 0.6, background, 0.4, 0)

# 展示图片需要RGB再次转为BGR(如果单纯展示图片前面加载图片就不用转了),如果需要图片保存则需要保存为RGB格式  
cur_img = cv2.cvtColor(merged,cv2.COLOR_RGB2BGR)  
cv2.imshow("merged", cur_img)  
  
# 按q退出,或者指定waiKey()指定时长(毫秒)后自动退出,为0则不退出,按键q退出  
if cv2.waitKey(0) & 0xFF == ord('q'):  
    cv2.destroyAllWindows()

效果图

在这里插入图片描述

边界填充

其实就是指定上下左右四个方向应该填充什么颜色。

img = cv2.imread("person.jpg")  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

# 填充的区域大小:上下左右
top, bottom, left, right = (50, 50, 50, 50)  

# 不同的填充策略
# 1. 复制法,就是直接把边缘的像素复制
replicate = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)  
# 2. 反射法,比如 321|12345|543
reflect_101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT_101)  
# 3. 反射法101,比如 432|12345|4321
reflect101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT101)  
# 4. 包装法,比如 345|12345|123
wrap = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_WRAP)  
# 5. 常量值填充,通过value指定常量值
constant = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT,value=255)  
  
images = [img, replicate, reflect_101, reflect101, wrap, constant]  
titles = ["ORIGINAL", "REPLICATE", "REFLECT_101", "REFLECT101", "WRAP", "CONSTANT"]  

# matplot绘图查看结果
rows, cols = 2, 3  
for i in range(rows * cols):  
    plt.subplot(int(f"{rows}{cols}{i+1}")), plt.imshow(images[i], 'gray'), plt.title(titles[i])  
    plt.axis('off')  
    plt.xticks([])  
    plt.yticks([])  
  
plt.show()

效果图

在这里插入图片描述

数值计算

img = cv2.imread('person.jpg')  
# 每个像素点的数值都+30,如果超过255则%256,比如刚好256则会变成黑色0  
img2 = img + 30  
  
# 直接相加,如果超过则固定数值255  
img3 = cv2.add(img, img)

图像阈值

ret, dst = cv2.threshold(src, thres, maxval, type)
  • ret:这个返回值是实际使用的阈值。如果type参数中使用了cv2.THRESH_OTSUcv2.THRESH_TRIANGLE,则ret是自动计算得到的最佳阈值,而thresh参数在这种情况下被忽略。如果不使用这些自动阈值计算方法,ret将与thresh参数的值相同。
  • dst:输出图
  • src:输入图
  • thresh:阈值数值
  • maxval:超出阈值后应该设置的值,或者小于,根据type策略决定
  • type:策略类型
    • cv2.THRESH_BINARY:如果像素值大于阈值,则像素值被设置为maxval;否则,像素值被设置为0。
    • cv2.THRESH_BINARY_INV:这是cv2.THRESH_BINARY的反向操作。
    • cv2.THRESH_TRUNC:如果像素值大于阈值,则像素值被设置为阈值;否则,像素值保持不变。
    • cv2.THRESH_TOZERO:如果像素值大于阈值,则像素值保持不变;否则,像素值被设置为0。
    • cv2.THRESH_TOZERO_INV:上面的反向操作
    • cv2.THRESH_OTSU:自动选择最佳阈值的方法。
    • cv2.THRESH_TRIANGLE:OpenCV 4.x中引入的一个选项,它使用了一种基于图像直方图的三角形方法来寻找最佳全局阈值。
img = cv2.imread('person.jpg')  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

# 不同阈值设置得到的结果图
ret, dst1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)  
ret, dst2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)  
ret, dst3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)  
ret, dst4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)  
ret, dst5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)  
  
images = [img, dst1, dst2, dst3, dst4, dst5]  
titles = ["ORIGINAL", "BINARY", "BINARY_INV", "TRUNC", "TOZERO", "TOZERO_INV"]  
explains = ["原图", "大于则为阈值,否则为0", "小于则为阈值,否则为0", "大于阈值则设置为阈值", "小于阈值则设置为0",  
            "大于阈值则设置为0"]  
  
rows = 2  
cols = 3  

# plt绘制图
for i in range(rows * cols):  
    plt.subplot(int(f"{rows}{cols}{i + 1}")), plt.imshow(images[i]), plt.title(titles[i])  
    # 在图片下方添加注释  
    plt.text(x=0.5, y=-0.1, s=explains[i], fontsize=10,  
             ha='center', va='top', transform=plt.gca().transAxes)  
    plt.axis('off')  
    plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

图像处理

1. 均值滤波

将像素点周围的数加起来计算平均值,我们设置的大小则为滤波核,或者叫卷积核,单词为kernel,在参数里边为ksize

在这里插入图片描述

这么计算的结果更像是把所有的像素点按照卷积核大小进行了一个平滑处理,当卷积核越大,影响的面积就越大,响应的平均后的值越均匀,给人的视觉效果就是更模糊

# 卷积3x3对应下图中间
ret1 = cv2.blur(noisy, (3, 3))
# 卷积4x4对应下图左上
ret1 = cv2.blur(noisy, (3, 3))

# 第一叫均值滤波,第二个叫方框滤波,结果完全是一样的
ret1 = cv2.blur(noisy, (3, 3))  
# 参数-1输出值像素深度保持一致,深度值得的是表示像素的位数,比如8位,16位,32位
ret2 = cv2.boxFilter(noisy, -1, (3, 3), normalize=True)

效果图
在这里插入图片描述

2. 高斯滤波

高斯滤波区别均值滤波的核心点在于权重概念,离像素点越远的位置权重比越小。

在这里插入图片描述

高斯滤波的核心是正态分布,然后计算权重得出的,高斯滤波处理的图片会比均值滤波更清晰因为距离越远受响应程度越小。

img = cv2.imread('person_noisy.png')  
  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)  
  
ret1 = cv2.blur(img, (25, 25))  
# 最后一个参数为sigma=0,标准差设置0让cv2自己计算合理的标准差  
ret2 = cv2.GaussianBlur(img, (25, 25), 0)  
  
plt.figure(figsize=(12,4))  
plt.subplot(131), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(132), plt.imshow(ret1), plt.title("均值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(133), plt.imshow(ret2), plt.title("高斯滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.tight_layout()  
plt.show()

效果图

在这里插入图片描述

3. 中值滤波

中值滤波计算方式最简单,将滤波核里边的数据取出来排序后取中间值,作为代替的值。

在这里插入图片描述

对于上面这种特殊例子,255永远是最大值,因此直接被过滤掉了。

img = cv2.imread('person_noisy.png')  
  
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  
  
ret1 = cv2.blur(img, (25, 25))  
# 最后一个参数为sigma=0,标准差设置0让cv2自己计算合理的标准差  
ret2 = cv2.GaussianBlur(img, (25, 25), 0)  
# 参数比较特殊,实际25等价上面的元组(25, 25)  
ret3 = cv2.medianBlur(img, 25)  
  
plt.subplot(221), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(222), plt.imshow(ret1), plt.title("均值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(223), plt.imshow(ret2), plt.title("高斯滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(224), plt.imshow(ret3), plt.title("中值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.tight_layout()  
plt.show()

效果图
在这里插入图片描述

形态学

1. 腐蚀操作

所谓腐蚀操作,是对图像处理效果的描述,原理则是根据给定的卷积核结构元(structuring element)遍历图像,然后取最小值赋值给目标像素点,最后得到一个处理后的结果矩阵。

计算原理遵循两个点:

  1. 卷积核完全处在图像内,确定的中心点就是目标点(是否需要腐蚀)
  2. 如果在这个卷积核区域内,只要存在最小值通常为二值图,也即是黑色0,则腐蚀中心点(也就是赋值最小值。)

示意图如下

其中图中框选区域就是卷积核结构元,就是一个3*3的矩阵,可见,只要白色区域不能完全覆盖卷积核,则中心点被设置为背景色黑色0。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
kernel = np.ones((90, 90), dtype=np.uint8)  
  
ret = cv2.erode(img, kernel, iterations=1)  
# 错误的使用,这实际上是利用卷积结构为一维数组,数据元素为5进行卷积运算,也就是说,1*2两个像素点大小的卷积核!!!  
# ret = cv2.erode(img, (5,5), iterations=9)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("腐蚀前(原图)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("腐蚀后)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()  

效果图
在这里插入图片描述

Note: 这里有个需要特别指明的点,在使用上,卷积核是一个矩阵结构,只不过里边的数都是1,如果错误使用,比如我这样👇

ret = cv2.erode(img, (5,5), iterations=9)

实际上是指定卷积核为1*2像素的一个长方形,里边元素是5,也就是对每个1*2大小的像素块的值乘5,导致结果并不会判断最小值0,也就是下面的结果,腐蚀效果看不到,实际上就腐蚀了大小为2的很小一个像素点。

错误的结果图:
在这里插入图片描述

总结:所谓腐蚀操作,**就是利用卷积核(也叫结构元)进行移动,只要所到之处没有被白色完全包裹,就会腐蚀中心点,**因此对于上面规整的黑白图,卷积核为10,实际就是白色区域减少10个白色像素点,如果设置20则减少20个白色像素点。

另外,所谓迭代次数,就是在操作后的基础上再次移动的意思。如果卷积核设置足够大,可以一次腐蚀操作直接全部腐蚀完。

在这里插入图片描述

如果卷积核大小完全覆盖白色区域,且在图片内,则一次腐蚀完毕!

2. 膨胀操作

如果理解了腐蚀操作,那么膨胀操作就很好理解了,完全是一个相反的操作,腐蚀操作是赋值卷积核内最小的数值,膨胀操作则恰恰相反,赋值最大的值也就是视觉上的白色。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
# 生成卷积核矩阵
kernel = np.ones((110, 110), dtype=np.uint8)

# 膨胀操作  
ret = cv2.dilate(img, kernel, iterations=1)  

# 输出结果图
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)

plt.subplot(121), plt.imshow(img), plt.title("膨胀前(原图)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("膨胀后"), plt.axis('off'), plt.xticks([]), plt.yticks([])

plt.show()

效果图
在这里插入图片描述

3. 开运算和闭运算

3.1 开运算

开运算(先腐蚀,再膨胀)。

在这里插入图片描述

这么看可能会认为这是无效的操作,看下面的图👇

在这里插入图片描述

可以看到,如果原图的白色很细小,不影响整体的腐蚀,那么开运算就可以达到去除杂质的效果。

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((90, 90), dtype=np.uint8)  
  
# 开运算  
ret = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("开运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

3.2 闭运算

闭运算(先膨胀,再腐蚀)。

在这里插入图片描述

闭运算并不能做到类似开运算的结果,相反他会让原来的图形轮廓变大(不同的图闭运算效果也不同)

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((90,90), dtype=np.uint8)  
  
# 闭运算  
ret = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("闭运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

4. 梯度运算

梯度运算(膨胀-腐蚀)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((5,5), dtype=np.uint8)  
  
# 梯度运算  
ret = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("梯度运算运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

5. 礼帽和黑帽

5.1 礼帽

礼帽(原图-开运算结果)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((95, 95), dtype=np.uint8)  
  
# 礼帽运算(原图-开运算结果)  
ret = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("礼帽"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

5.2 黑帽

黑帽(闭运算结果-原图)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((95, 95), dtype=np.uint8)  
  
# 黑帽运算(闭运算结果-原图)  
ret = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("黑帽"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图

在这里插入图片描述

图形梯度

注意: 这里的梯度,指得是图像强度或颜色的方向变化。 他是用来衡量变化幅度的,比如黑色到白色,直接变化255,就是很大的一个梯度,可以理解为楼梯的陡峭程度;而形态学中的梯度是一种运算操作。

一个像素点左边跟右边的差值就是梯度,这个就是x轴方向的梯度,上边跟下边的差值,就是y轴方向的梯度。而得出这个差异值的计算方法,就是算子,OpenCV提供了三种算子:SobelScharr和Laplacian,高大上的名词就叫梯度滤波器高通滤波器

这个本质跟形态操作是一样的,只是设置的卷积矩阵值不同。

在这里插入图片描述

我们通过设置卷积矩阵里边元素的数值,做到右边-左边差异值的结果作为当前像素的梯度表示。

中心点梯度 = [ 0 0 255 0 0 255 0 0 255 ] ∗ 卷积矩阵 [ − 1 0 1 − 2 0 2 − 1 0 1 ] = 255 ∗ 1 + 255 ∗ 2 + 255 ∗ 1 = 255 中心点梯度= \begin{bmatrix} 0&0&255\\ 0&0&255\\ 0&0&255 \end{bmatrix}* 卷积矩阵 \begin{bmatrix} -1&0&1\\ -2&0&2\\ -1&0&1\\ \end{bmatrix} = 255*1 + 255 * 2 + 255*1 = 255 中心点梯度= 000000255255255 卷积矩阵 121000121 =2551+2552+2551=255

动态效果图

1. Sobel算子

当我们设置卷积矩阵为下面的时候,就是Sobel算子。

S o b e l [ − 1 0 1 − 2 0 2 − 1 0 1 ] Sobel \begin{bmatrix} -1&0&1\\ -2&0&2\\ -1&0&1 \end{bmatrix} Sobel 121000121

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定为-1,x轴计算  
ret = cv2.Sobel(img, -1, 1, 0)   

# 转换通道,仅用于展示用
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("Sobel算子结果"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

这里存在两个问题:

  1. 没有计算y轴的梯度
  2. 右边的梯度被忽略了

关于右边的梯度问题,这是因为我们设置的深度为-1,什么是深度? 我们一直默认颜色数值表示是0~255就是因为默认是uint8数据类型为无符号的8位,因此当黑色-白色为负数时,被截断为0,同理当计算超过255时则被截断为255。

但这显然不是我们期望的结果,因为我们想要整张图的梯度而无关正数和负数,因此我们需要将深度改为更多位数的表示,比如32F,64F,即带符号的32位表示,64位表示。同时对梯度结果取绝对值,排除负数。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定深度为有符号64位,x轴计算  
ret = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
# 将梯度结果取绝对值  
ret = cv2.convertScaleAbs(ret)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("Sobel算子结果"), plt.axis('off'), plt.xticks([]), plt.yticks([])

效果图

在这里插入图片描述

对y轴的梯度计算同理,我们直接看最后的使用

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定深度为有符号64位,x轴计算  
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
# Sobel算子,这里深度指定深度为有符号64位,x轴计算  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
# Sobel算子,这里深度指定深度为有符号64位,x,y轴同时计算  
xy_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 1)  
xy_gradient = cv2.convertScaleAbs(xy_gradient)  
  
# x轴和y轴的融合结果(x轴权重0.5,y轴权重0.5),标量值设置为0 ==> dst=src1⋅α+src2⋅β+γ  
x_add_y_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)

# 图片展示代码略

效果图

在这里插入图片描述

可见,同时对xy轴计算梯度的效果并不好,因此最好分别计算x轴和y轴最后加权平均。

2. Scharr算子

有了前面的基础,这里就很简单了,当我们设置卷积矩阵为下面的时候,就是Scharr算子。

S c h a r r [ − 3 0 3 − 10 0 10 − 3 0 3 ] Scharr \begin{bmatrix} -3&0&3\\ -10&0&10\\ -3&0&3 \end{bmatrix} Scharr 31030003103

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Scharr算子,这里深度指定深度为有符号64位,x轴计算  
x_gradient = cv2.Scharr(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
# Sobel算子,这里深度指定深度为有符号64位,x轴计算  
y_gradient = cv2.Scharr(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)

# 如果Sobel指定ksize=-1就是等价Scharr的用法
# cv2.Sobel(src,cv2.CV_64F,1,0,ksize=-1)

# x轴和y轴的融合结果(x轴权重0.5,y轴权重0.5),标量值设置为0 ==> dst=src1⋅α+src2⋅β+γ
x_add_y_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)

3. Laplacian算子

当卷积核为下面的数值时,则为Laplacian算子。

L a p l a c i a n [ 0 1 0 1 − 4 1 0 1 0 ] Laplacian \begin{bmatrix} 0&1&0\\ 1&-4&1\\ 0&1&0 \end{bmatrix} Laplacian 010141010

4. 三种算子的比较


img = cv2.imread('person.jpg', cv2.IMREAD_GRAYSCALE)  

# Sobel算子
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
sobel_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)  
  
# Scharr算子, Sobel函数指定ksize=-1就是Scharr  
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=-1)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=-1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
scharr_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)  

# Laplacian算子
laplacian_gradient = cv2.Laplacian(img, cv2.CV_64F)  
laplacian_gradient = cv2.convertScaleAbs(laplacian_gradient)

# 图片展示代码略

效果图
在这里插入图片描述

总结: Sobel算子主要用于边缘检测,Scharr算子同理但是他更能捕捉细节,二者对噪音点的抵抗都还可以,Laplacian算子噪音点对识别的影响较大,但是能够提供更清晰的边缘效果。


参考链接

[1]:B站视频&opencv从入门到实战
[2]:官网Docs4.10.0
[3]:中文文档
[4]:原来卷积是这么计算的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2137393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Science:学术大咖揭秘审稿内幕,你的稿件在审稿人那里经历了什么?

我是娜姐 迪娜学姐 ,一个SCI医学期刊编辑,探索用AI工具提效论文写作和发表。 同行评审是每篇论文能够顺利发表的必经之路。Science期刊采访了来自全球各领域研究人员,分享关于他们作为审稿人,为什么接受审稿、给出审稿意见的依据、…

怎么让手机ip地址变化?介绍几种实用方法

随着网络技术的发展,IP地址作为网络设备的唯一标识,其变动对于保护个人隐私、规避网络限制等方面具有重要意义。本文将介绍几种实用的方法,帮助用户实现手机IP地址的变化,并提醒注意事项。 一、连接不同的WiFi网络‌ 连接不同的W…

记一次实战中对fastjson waf的绕过

最近遇到一个fastjson的站,很明显是有fastjson漏洞的,因为type这种字符,fastjson特征很明显的字符都被过滤了 于是开始了绕过之旅,顺便来学习一下如何waf 编码绕过 去网上搜索还是有绕过waf的文章,下面来分析一手&a…

ICM20948 DMP代码详解(21)

接前一篇文章:ICM20948 DMP代码详解(20) 上一回终于解析完了inv_icm20948_read_mems_reg函数,本回回到inv_icm20948_initialize_lower_driver函数中,继续往下解析该函数接下来的内容。为了便于理解和分析,在…

[进阶]面向对象之多态(练习)

需求: //父类animal package polymorphism.Test;public abstract class Animal {private int age;private String color;public Animal() {}public Animal(int age, String color) {this.age age;this.color color;}public int getAge() {return age;}public void setAge(i…

深入理解 C 语言中的结构体 —— 原理与实践

引言 在 C 语言中,结构体是一种非常强大的数据类型,用于组织不同类型的数据成员。通过结构体,我们可以创建复杂的数据结构,用于表示现实生活中的对象。本文将详细介绍 C 语言中结构体的基本概念、语法、使用方法以及一些高级主题…

c++中的二叉搜索树

一概念: 静图展示: 动图展示: ①左子树不为空,则左子树节点值小于根节点值。 ②右子树不为空,则右子树节点值大于根节点值。 ③左右子树均为二叉搜索树。 ④对于它可以插入相等的也可以插入不相等的,这里如果插入的…

MATLAB系列02:MATLAB基础

MATLAB系列02:MATLAB基础 2. MATLAB基础2.1 变量和数组2.2 MATLAB变量的初始化2.2.1 用赋值语句初始化变量2.2.2 用捷径表达式赋值2.2.3 使用内置函数来初始化2.2.4 使用关键字input来初始化 2.3 多维数组2.3.1 创建多维数组2.3.2 多维数组在内存中的存储2.3.3 用单…

深入理解FastAPI中的root_path:提升API部署灵活性的关键配置

在Web开发领域,FastAPI因其高性能、易于使用和类型提示功能而备受开发者喜爱。然而,当涉及到在生产环境中部署FastAPI应用程序时,我们常常需要面对一些挑战,比如如何正确处理代理服务器添加的路径前缀。这时,root_path…

关于java同步调用多个接口并返回数据

在现代软件开发中,应用程序经常需要与多个远程API接口进行交互以获取数据。Java作为一种流行的编程语言,提供了多种方式来实现这一需求。本文将探讨如何在Java中同步调用多个API接口,并有效地处理和返回数据。 同步调用的必要性 在某些场景下…

vue table id一样的列合并

合并场景:如果id一样,则主表列合并,子表列不做合并,可实现单行、多行合并,亲测!!! 展示效果如图示: 组件代码: // table组件 :span-method"objectSpa…

网络安全 DVWA通关指南 DVWA SQL Injection (Blind SQL盲注)

DVWA SQL Injection (Blind) 文章目录 DVWA SQL Injection (Blind)Low布尔盲注时间盲注sqlmap MediumHighImpossible 参考文献 WEB 安全靶场通关指南 Low 0、分析网页源代码 <?phpif( isset( $_GET[ Submit ] ) ) {// Get input$id $_GET[ id ];// Check database$geti…

基于spring boot的车辆故障综合服务平台设计与实现----附源码 73314

摘 要 近年来&#xff0c;随着社会科技的不断发展&#xff0c;人们的生活方方面面进入了信息化时代。计算机的普及&#xff0c;使得我们的生活更加丰富多彩。本论文基于Spring Boot框架&#xff0c;设计并实现了一个车辆故障综合服务平台&#xff0c;旨在提供便捷、高效的汽车…

c++类模板为什么不能编译到动态库中来使用

在使用c的时候&#xff0c;我们习惯于将类的定义声明在头文件中&#xff0c;即.h文件&#xff1b;将类函数的实现定义在源文件中&#xff0c;即.cpp文件。如果我们要提供的是一个动态库&#xff0c;那么这种方式更常用&#xff0c;使用动态库的时候&#xff0c;包含头文件&…

如何注册Liberty大学并获取Perplexity Pro

俗称白嫖 Perplexity Pro 会员 如何注册Liberty大学并获取Perplexity Pro 1. 访问官网 首先&#xff0c;进入Liberty大学官网 https://www.liberty.edu&#xff0c;点击“Apply”按钮。 2. 选择课程 选择“Online”课程&#xff0c;选择“Certificate”&#xff0c;然后随便…

深入理解Docke工作原理:UnionFS文件系统详解

在容器技术的世界中&#xff0c;文件系统的设计和实现是其关键组成部分&#xff0c;影响着镜像的构建效率、容器的启动速度以及资源的利用率。**UnionFS&#xff08;联合文件系统&#xff09;**作为Docker的核心文件系统技术&#xff0c;通过其独特的分层结构和写时复制&#x…

5 - ZYNQ SDK学习记录(2)

文章目录 1 Vivado工程基本设计2 Vivado工程位置不变2.1 修改设计1 - 增加PS侧QSPI外设2.2 修改设计2 - 增加PL侧AXI GPIO外设2.3 总结 3 Vivado工程位置变动3.1 先修改BD后打开SDK3.2 先打开SDK后修改BD3.3 总结 1 Vivado工程基本设计 Step 1&#xff1a; Vivado版本Vivado …

【观影聊数学】聊聊电影《孤注一掷》中的数学逻辑

反电诈题材影片《孤注一掷》取材于真实案例&#xff0c;揭秘了境外电信网络诈骗黑色产业链的骇人内幕。境外诈骗集团往往以高薪招聘为诱饵&#xff0c;吸引有发财梦的人去境外淘金&#xff0c;一旦人们走出国门&#xff0c;跳入犯罪分子设下的陷阱里&#xff0c;等待他们的将是…

【python爬虫】之scrapy框架介绍

一.什么是Scrapy&#xff1f; Scrapy是一个为了爬取网站数据&#xff0c;提取结构性数据而编写的应用框架&#xff0c;非常出名&#xff0c;非常强悍。所谓的框架就是一个已经被集成了各种功能&#xff08;高性能异步下载&#xff0c;队列&#xff0c;分布式&#xff0c;解析&a…

SpringBoot开发——使用@Slf4j注解实现日志输出

文章目录 1、Lombok简介2、SLF4J简介3、实现步骤3.1 创建SpringBoot项目3.2 添加依赖3.3 使用 Slf4j 注解3.4 输出日志信息 4、结论 在现代Java开发中&#xff0c;日志记录是至关重要的。它不仅帮助开发者调试代码&#xff0c;还便于监控系统运行状态和性能。 Lombok 和 SLF4J …