目录

OpenCV2-图像基本操作-阈值与平滑处理-形态学-梯度运算

OpenCV2-图像基本操作-阈值与平滑处理-形态学-梯度运算

https://i-operation.csdnimg.cn/images/cf31225e169b4512917b2e77694eb0a2.pngOpenCV图像处理核心技术详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

1. 图像基本操作

1.0 基本操作

https://i-blog.csdnimg.cn/direct/edb6189d141641ddb6ebe46a12c07146.png

import cv2 #opencv读取的格式是BGR
import matplotlib.pyplot as plt
import numpy as np 
%matplotlib inline

img=cv2.imread('../cat.jpg')

%matplotlib inline 是一个特殊的 IPython 魔法命令
执行该命令后,后续所有 plt.plot()、plt.imshow() 等绘图命令的结果会直接显示在代码单元格的下方
图像会被保存为静态图片嵌入到笔记本中,方便保存和分享
不需要再额外调用 plt.show() 来显示图像(不过调用了也不会有问题)

https://i-blog.csdnimg.cn/direct/89eb59b5fe514509a87ae15028e7aadb.png

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

过了十秒,弹窗就自动消失了

https://i-blog.csdnimg.cn/direct/c6c16b02088d4722ae6481820ac898c3.png

def cv_show(name,img):
    cv2.imshow(name,img) 
    cv2.waitKey(0) 
    cv2.destroyAllWindows()

创建了一个自动显示的函数

https://i-blog.csdnimg.cn/direct/92f18d6022164a298958eb4e02d88407.png

img=cv2.imread('../img/cat.jpg',cv2.IMREAD_GRAYSCALE)
img

https://i-blog.csdnimg.cn/direct/edc2b00fb3074fb3b4c4e38d8e9bcb82.png

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

https://i-blog.csdnimg.cn/direct/d77845a1af8542369afdc7555ee052d0.png

#保存
cv2.imwrite('mycat.png',img)

https://i-blog.csdnimg.cn/direct/87dd087976854f68968ff9ff7c6db6b6.png

type(img)
img.size
img.dtype

https://i-blog.csdnimg.cn/direct/521f9b46a310471386f2f9e24f2cf05e.png

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:
        break
    if 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

https://i-blog.csdnimg.cn/direct/32f31411e9e4423abf5916b77a071d71.png

这样就变成了灰度视频了

        if cv2.waitKey(100) & 0xFF == 27:

如果是这样的话,就很慢了

1.2 截取部分图像数据

#截取部分图像数据
img=cv2.imread('../img/cat.jpg')
cat=img[0:200,0:200] 
cv_show('cat',cat)

https://i-blog.csdnimg.cn/direct/e9489ab6209840da9873c06813c54789.png

1.3 颜色通道提取

b,g,r=cv2.split(img)

https://i-blog.csdnimg.cn/direct/1ca164b03f424c42857108a55d447143.png
https://i-blog.csdnimg.cn/direct/489f1cec68b54faa94fbbfd9d3582592.png

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)

https://i-blog.csdnimg.cn/direct/445aef0566bf402881adf26fda8cde90.png

这样就是只有R了

# 只保留G
cur_img = img.copy()
cur_img[:,:,0] = 0
cur_img[:,:,2] = 0
cv_show('G',cur_img)

https://i-blog.csdnimg.cn/direct/0accbdd41f1243988804f7f052a50f46.png

1.4 边界填充

https://i-blog.csdnimg.cn/direct/ab5608d3c1064bdb9ffe381ae5fd18c7.png

https://i-blog.csdnimg.cn/direct/c79ea8f57a994232b334bbbb39915957.png

#上下左右分别填充的大小
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 个位置

https://i-blog.csdnimg.cn/direct/cd857282d5c147e9be3694f738e40f82.png

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

https://i-blog.csdnimg.cn/direct/09ce53aef618496a8a5973d891958d02.png

#相当于% 256
(img_cat + img_cat2)[:5,:,0] 

https://i-blog.csdnimg.cn/direct/85df6e5779b0477f9fdeea2c5f118fe3.png

142+152=294
294%256=38

cv2.add(img_cat,img_cat2)[:5,:,0]

https://i-blog.csdnimg.cn/direct/5bf7f805d1554d64b2289787f79f95e8.png
142+152=294~~255

1.6 图像融合

img_cat + img_dog

这样是不行的额,因为行列不一样
https://i-blog.csdnimg.cn/direct/424ece37f68e441c8b30c73c1bfa8326.png
我们把它们变为一样的

img_dog = cv2.resize(img_dog, (500, 414))
img_dog.shape

https://i-blog.csdnimg.cn/direct/fa40d89e4ffd48a6b861648ba7cba886.png

res = cv2.resize(img, (0, 0), fx=3, fy=1)
plt.imshow(res)

(0, 0) 是 dsize 参数的值,表示不直接指定输出图像的尺寸,而是通过后面的缩放因子 fx 和 fy 来计算新尺寸
fx=3 表示水平方向放大 3 倍
fy=1 表示垂直方向保持原尺寸不变
https://i-blog.csdnimg.cn/direct/b85f36f7f8ea49489073550c0fdd9999.png

res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)

这个表示按照权值相加
但是前提肯定是长宽相同

plt.imshow(res)
plt.show()

https://i-blog.csdnimg.cn/direct/5328e806b2da45bcb197db023dad0e09.png

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_TOZEROcv2.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的反转
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是阈值
https://i-blog.csdnimg.cn/direct/9066807ce093491498fb7b14e6f37ea6.png
THRESH_BINARY_INV 中INV表示反转

2.2 图像平滑

#%% md
![image.png](attachment:image.png)
#%%
img = cv2.imread('../img/lenaNoise.png')

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/82101219e38b45078b2f23d8a9804543.png

有些白点—》去掉

https://i-blog.csdnimg.cn/direct/f2bf869b929d42f8be14e7c860721b9b.png

我们这里要用到均值滤波,意思就是把204变为周围3*3的平均值,每个值都这样处理
https://i-blog.csdnimg.cn/direct/ad73c46feaa54c738be8320d736f4384.png

怎么计算呢,我们可以把这个33的矩阵去乘以33的全1矩阵

# 均值滤波
# 简单的平均卷积操作
blur = cv2.blur(img, (3, 3))

cv2.imshow('blur', blur)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/94a4e3b21f44461b98df71f659166841.png
发现白点没有那么明显了

# 方框滤波
# 基本和均值一样可以选择归一化
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),从而出现图像过曝(白块)现象。
https://i-blog.csdnimg.cn/direct/1251657df9dd43c08f5210884337f890.png

所以得到的结果和均值滤波一样

# 方框滤波
# 基本和均值一样可以选择归一化
box = cv2.boxFilter(img,-1,(3,3), normalize=False)

cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/59c827bcb69649c1b204afb785731bbb.png

False就没有除以9了,直接超级白了

2.3 高斯与中值滤波

https://i-blog.csdnimg.cn/direct/1fc36cdabb16462ea4d42fad945aaa79.png
高斯滤波就是这样的,近的就占的比重大点

# 高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布相当于更重视中间的
aussian = cv2.GaussianBlur(img, (5, 5), 1)  

cv2.imshow('aussian', aussian)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/212e3a4b69114203934911180348aea8.png

中值滤波就是3*3数字排完序之后,取中间的值

# 中值滤波
# 相当于用中值代替
median = cv2.medianBlur(img, 5)  # 中值滤波

cv2.imshow('median', median)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/1f47f986038c4e7ebb09717e6132ac30.png
5表示5*5

基本上都不见了

因为噪音点少,而且噪音点比较亮,值比较大,所以排序选中间一般可能选不到它
但是取平均的话,很有可能就会偏亮,因为噪音点值大

# 展示所有的
res = np.hstack((blur,aussian,median))
print (res)
cv2.imshow('median vs average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

这个就是图像拼接,hstack表示横着拼接

https://i-blog.csdnimg.cn/direct/a2ca95bca8b240739d1ecc8a221d2d4c.png

vstack表示竖着拼接

3. 图像形态学操作

3.1 形态学-腐蚀操作

img = cv2.imread('../img/dige.png')

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/2d374a52dc7e4de4b237994ff1e6d3c4.png

这就是图像操作的前提,图像数据是二值的

意思就是只有两种颜色
怎么去掉那些虚线呢–》腐蚀操作

pie = cv2.imread('../img/pie.png')

cv2.imshow('pie', pie)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/c0943cf0dfdb44d0867cce01c57d472c.png

https://i-blog.csdnimg.cn/direct/2a86c38a447e47c085a4196aae9f8825.png

腐蚀操作意思就是当一个点的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()

https://i-blog.csdnimg.cn/direct/ede55e0c99ef4c32b862b2c1343337c4.png

kernel = np.ones((3,3),np.uint8) 
erosion = cv2.erode(img,kernel,iterations = 3)

cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/fe338a585a5a41bab45270d1bb8319cc.png
(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()

https://i-blog.csdnimg.cn/direct/fe5b6d56bd5a475db87dd34f2ecb94f9.png

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()

https://i-blog.csdnimg.cn/direct/1278295ab35648369bd574fc8676f4ec.png
怎么变胖一点呢

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()

https://i-blog.csdnimg.cn/direct/2139ad61739a4cb1ba319992ed9eed3b.png

就是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()

https://i-blog.csdnimg.cn/direct/d431dc1397df4986a9f7c7642a9a37bb.png

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()

https://i-blog.csdnimg.cn/direct/9c37be68b2fd4b908dd7a1af564c431f.png

# 先膨胀再腐蚀
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()

https://i-blog.csdnimg.cn/direct/03f6882241f64fa5abe79c1331236a78.png

所以不能去掉毛刺

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()

https://i-blog.csdnimg.cn/direct/44784155b7884d368d4ae7387acc2b63.png

这个是正常膨胀和腐蚀之后的结果

梯度运算就是膨胀-腐蚀,那么一相减的话,中间那部分相同的部分就变为黑色了

gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel)

cv2.imshow('gradient', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/ebc5301b9e2b42bfb52ab0b650c77221.png

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()

https://i-blog.csdnimg.cn/direct/3e1e0a66e9b4418e800f7a0b879bb1fe.png

#黑帽
img = cv2.imread('../img/dige.png')
blackhat  = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat ', blackhat )
cv2.waitKey(0)
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/3befaff198e74a4c90384cd09993aa72.png

4. 图像梯度运算

4.1 图像梯度-Sobel算子

img = cv2.imread('../img/pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()

https://i-blog.csdnimg.cn/direct/e96a42ff328a450f92d3910b5333681c.png

什么是梯度呢,梯度就是一个点上,有不同颜色,就是有梯度—》横着竖着,分别看有没有梯度
https://i-blog.csdnimg.cn/direct/861185ac7bc84cd7b5e00db72e0fb5f4.png

假设我们选的点是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表示只算水平的梯度,不算竖直的梯度

https://i-blog.csdnimg.cn/direct/3691be4ec4694188970516b3bac61646.png
因为算的是水平的,是右边减去左边,那么偏左的时候,白色-黑色是大于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,所以里面存的是负数,再取绝对值,就对了
https://i-blog.csdnimg.cn/direct/0806cdbab8e644b89f66f253f561a335.png

https://i-blog.csdnimg.cn/direct/8a0d5bd28f2349f590cfe29d9134ff43.png

这个就是总的梯度计算
或者Gx的绝对值+Gy的绝对值

sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)  
cv_show(sobely,'sobely')

https://i-blog.csdnimg.cn/direct/f05b1301cabd40839d8abe409a60f3ee.png

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

就是0.5Gx+0.5Gy了

https://i-blog.csdnimg.cn/direct/73df19bf15394bc3b87e280aa6b24a7b.png

sobelxy=cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy) 
cv_show(sobelxy,'sobelxy')

一开始的时候,就一起计算呢

https://i-blog.csdnimg.cn/direct/4b073e0d5504472c932794fa3b3442e9.png

https://i-blog.csdnimg.cn/direct/176eeb94365341cb8d9a0c07df6786c7.png
所以说这就是为什么东南西北四个方向都没有白点的原因

不建议直接计算

img = cv2.imread('../img/lena.jpg',cv2.IMREAD_GRAYSCALE)
cv_show(img,'img')

https://i-blog.csdnimg.cn/direct/892e1e0eed344cb78ce819baddb69a2d.png
这是一个灰度图

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')

https://i-blog.csdnimg.cn/direct/a9a3dbe3ea5e40d8af55e2d8f2fead16.png
就是找出轮廓的作用了

# 不建议直接计算

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')

https://i-blog.csdnimg.cn/direct/cb9153e8528f41bcab1b47a96a79e33d.png
我们发现直接计算就效果不好了,直接计算会少很多的点

4.2 图像梯度-Scharr算子和laplacian算子

https://i-blog.csdnimg.cn/direct/d8e7d3c4aab44eec9a2beea8e7d6ba1b.png

发现这个差值更大了,但还是右边减去左边,下边减去上边

https://i-blog.csdnimg.cn/direct/241ce2aa2e7f487884d78a2decd3005a.png
这个是中间点和边缘点的差值
如果这个点不在边界—–》变黑
在边界——-》有变化了

#不同算子的差异
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')

这个就是上面讲的三个算法

https://i-blog.csdnimg.cn/direct/fefe8befedff4b868bd7709fd5b7264a.png

第二个描述得更细节了,你看那木头的纹路都弄出来了
Laplacian描述得一般

总结