计算机视觉

OpenCv简单应用

基本操作

  1. 图像读取:
1
2
3
4
5
# 图像读取有两种格式
# 1.cv2.IMREAD_COLOR 彩色图像
# 2.cv2.IMREAD_GRAYSCALE 灰色图像
img = cv2.imread("图像路径", 图像读取格式)
img.shape # === 例(414,500, 3)

​ 该方法返回值是一个 numpy 数组, 是一个三维数组

​ 其含义分别是 y(竖直 h),x(水平 w), BGR(三原色), 在opencv中的色彩是BGR 不是RGB

  1. 图像的显示
1
2
3
4
5
cv2.imshow("窗口名称", 图像数据)
# 窗口等待时间 如果设置为0的话则需要手动关闭
cv2.waitKey(0)
# 使用此方法可以在等待结束之后关闭窗口,
cv2.destoryAllWindows()
  1. 视频的读取

    • cv2.VideoCapture可以捕捉摄像头,用数字来控制不同的设备,例如0,1

    • 如果是视频文件,直接指定好路径即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vc = cv2.VideoCapture("test.mp4")

# 检查是否打开正确
if vc.isOpened():
# vc.read()返回两个值,
# 1.是一个boolean类型的值,其含义是第一帧是否读取到了
# 2.是一个img类型的,和上面的一样,是一张图片
open, frame = vc.read()
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)
# 判断按键是否等于27(esc键) & 0xFF是为了消除其他键的干扰
if cv2.waitKey(1) & 0xFF == 27:
break
vc.release()
cv2.destroyAllWindows()
  1. 图像的截取
1
2
img = cv2.imread("cat.jpg")
cat = img[0:200, 0:200]
  1. 颜色通道的获取
1
2
# 因为Opencv的颜色为BGR
b, g, r = cv2.split(img)
  1. 图像的合并:
1
img = cv2.merge((b, g, r))
  1. 图像颜色的改变

    1
    2
    3
    4
    5
    6
    7
    # 变成红色 只要把对应的蓝色和绿色通道变为0即可
    cur_img = img.copy()
    # B 通道
    cur_img[:,:,0] = 0
    # G 通道
    cur_img[:,:,1] = 0

  2. 边界的填充

    使用函数 copyMakeBorder(图像数据, 上扩展大小, 下扩展大小, 左扩展大小,右扩展大小, 填充方法)

    • BORDER_REPLICATE: 复制法,也就是复制最边缘的像素
    • BORDER_REFLECT: 反射法,对感兴趣的图像中的像素在两边进行复制,如: fedcba | abcdefgh | hgfedcb
    • BORDER_REFLECT_101: 反射法,也就是以最外边缘像素为轴,对称,例如,gfedcb | abcdefgh | gfedcba
    • BORDER_WRAP: 外包装法,cdefgh | abcdefgh | abcdefg
    • BORDER_CONSTANT: 常量法,常数值填充
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 先确定上下左右扩展的大小
    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, borderType = cv2.BORDER_REFLECT)

    reflect_101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType = cv2.BORDER_REFLECT_101)

    wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType = cv2.BORDER_WRAP)

    constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType = cv2.BORDER_CONSTANT, value=0)

    图像结果展示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import matplotlib.pyplot as plt
    fig = plt.figure(figsize=(20,8), dpi = 80)
    plt.subplot(231), plt.imshow(img, "gray"), plt.title("ORGINAL")
    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(reflect_101, "gray"), plt.title("REFLECT_101")
    plt.subplot(235), plt.imshow(wrap, "gray"), plt.title("WARP")
    plt.subplot(236), plt.imshow(constant, "gray"), plt.title("CONSTANT")
    plt.show()

    image-20220702164831284

  3. 数值计算规则

    使用广播原则,总的加多少,对应的下部分就加多少

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    img_cat = cv2.imread("cat.jpg")
    img_dog = cv2.imread("dog.jpg")

    img_cat2 = img_cat + 10
    print(img_cat[:5,:,0])
    '''
    array([[142, 146, 151, ..., 156, 155, 154],
    [108, 112, 118, ..., 155, 154, 153],
    [108, 110, 118, ..., 156, 155, 154],
    [139, 141, 148, ..., 156, 155, 154],
    [153, 156, 163, ..., 160, 159, 158]], dtype=uint8)
    '''
    print(img_cat2[:5,:,0])
    '''
    array([[152, 156, 161, ..., 166, 165, 164],
    [118, 122, 128, ..., 165, 164, 163],
    [118, 120, 128, ..., 166, 165, 164],
    [149, 151, 158, ..., 166, 165, 164],
    [163, 166, 173, ..., 170, 169, 168]], dtype=uint8)
    '''
    # 可以看到img_cat2中每一个位置相较于img_cat中的位置都加了10

    在numpy中加减和在opencv中加减的规则还不一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 如果在numpy中相加之后的数据大小,超过255的话 相当于对255取余
    (img_cat + img_cat2)[:5,:,0]
    '''
    array([[ 38, 46, 56, ..., 66, 64, 62],
    [226, 234, 246, ..., 64, 62, 60],
    [226, 230, 246, ..., 66, 64, 62],
    [ 32, 36, 50, ..., 66, 64, 62],
    [ 60, 66, 80, ..., 74, 72, 70]], dtype=uint8)
    '''
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 在opencv中使用add操作的时候,当数值超过255的话,则按照255算
    cv2.add(img_cat , img_cat2)[:5,:,0]
    '''
    array([[255, 255, 255, ..., 255, 255, 255],
    [226, 234, 246, ..., 255, 255, 255],
    [226, 230, 246, ..., 255, 255, 255],
    [255, 255, 255, ..., 255, 255, 255],
    [255, 255, 255, ..., 255, 255, 255]], dtype=uint8)
    '''
  4. 图像大小的改变

    resize方法

    InputArray src :输入,原图像,即待改变大小的图像;
    OutputArray dst: 输出,改变后的图像。这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;
    dsize:输出图像的大小,

    其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。
    fx:width方向的缩放比例
    fy:height方向的缩放比例

    interpolation :这里只将interpolation设定为cv.INTER_AREA。值得注意的是,当缩小图片时,cv.INTER_AREA效果较好,当放大图片时,cv.INTER_CUBICcv.INTER_LINEAR效果较好。

    1
    2
    3
    4
    resize(InputArray src, OutputArray dst, Size dsize, 
    double fx=0, double fy=0, int interpolation=INTER_LINEAR )

    res = cv2.resize(img, (0,0), fx=3, fy=1)
  5. 图像的翻转和旋转

    • 图像的翻转:

      flip(src, flipCode[, dst])
      flipCode=0 表示上下翻,
      flipCode>0 表示左右翻转
      flipCode<0 上下+左右

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
dog=cv2.imread('cat.jpg')
img=cv2.flip(dog,0)
#下面的语句也能实现图片的翻转
#img=dog[::-1]
#左右翻转
#img=dog[::,::-1]
#上下+左右
#img=dog[::-1,::-1]
cv2.imshow('dog',img)
key=cv2.waitKey(0)
cv2.destroyAllWindows()
  • 图像的旋转:

    rotate(src, rotateCode[, dst])

    Rotate by 90 degrees clockwise (rotateCode = ROTATE_90_CLOCKWISE).

    Rotate by 180 degrees clockwise (rotateCode = ROTATE_180).

    Rotate by 270 degrees clockwise (rotateCode = ROTATE_90_COUNTERCLOCKWISE).

    COUNTERCLOCKWISE逆时针,CLOCKWISE顺时针

1
2
3
4
5
6
7
8
import cv2
import numpy as np
dog=cv2.imread('cat.jpg')
img=cv2.rotate(dog,cv2.ROTATE_90_COUNTERCLOCKWISE)

cv2.imshow('dog',img)
key=cv2.waitKey(0)
cv2.destroyAllWindows()

形态学的操作

腐蚀操作

cv2.erode(img, kernel, iterations)

1
2
3
4
5
6
7
img = cv2.imread("opencv.png")
cv_show("img", img)
# 确定核
kernel = np.ones((5,5), np.uint8)
# 进行腐蚀操作
erosion = cv2.erode(img, kernel, iterations = 1)
cv_show("erosion", erosion)

原先图像:

opencv

腐蚀操作之后的图像:

image-20220702170744002

我们可以看到 图像中的一些杂线就会被抹去,但是带来的负面影响就是图像本身也变细了

膨胀操作

cv2.dilate(需要膨胀的图像, 核, 迭代次数)

1
2
3
4
5
6
7
8
img = cv2.imread("opencv.png")
cv_show("img",img)

kernel = np.ones((3,3), np.uint8)

# 膨胀操作
dige_erosion = cv2.dilate(dige_erosion, kernel, iterations=4)
cv_show("dige_erosion", dige_erosion)

膨胀前的图像:

image-20220702171639941

膨胀后的图像:

image-20220702171709369

明显就变粗了许多

开运算

开运算就是把上面两种运算结合起来,

开: 先腐蚀在膨胀

1
2
3
4
5
6
7
# 开 : 先腐蚀在膨胀
img = cv2.imread(".png")

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

cv_show("img", opening)

开运算前的图像:

image-20220702171639941

开运算后的图像:

image-20220702171925751

从这里可以看出开运算既可以把杂边去掉也可以保持原来的图像损害不大

闭运算

闭运算和开运算相对:先膨胀在腐蚀:

1
2
3
4
5
6
7
闭运算:先膨胀,再腐蚀
img = cv2.imread("opencv.png")

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

cv_show("closing", closing)

闭运算之前的图像

image-20220702171639941

闭运算之后的图像:

image-20220702171639941

可以看出这两个图像几乎没有差别

梯度的运算

梯度:膨胀 - 腐蚀

cv2.morphologyEx(图像, cv2.MORPH_GRADIENT, kernel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 梯度:膨胀 - 腐蚀
pei = cv2.imread("pei.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))

cv_show("res", res)

# 梯度的计算
gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT,kernel)

cv_show("gradient", gradient)

膨胀和腐蚀操作的图像为:

image-20220702173843939

梯度的图像为:

image-20220702173918211

礼帽与黑帽

  • 礼帽:原始输入 - 开运算结果
    • 开运算可以消除暗背景下的高亮区域,那么如果用原图减去开运算结果就可以得到原图中灰度较亮的区域,所以又称白顶帽变换。
  • 黑帽:闭运算 - 原始输入
    • 闭运算可以删除亮背景下的暗区域,那么用原图减去闭运算结果就可以得到原图像中灰度较暗的区域,所以又称黑底帽变换。
1
2
3
4
5
6
7
8
9
10
# 礼帽
img = cv2.imread("opencv.png")
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

cv_show("tophat", tophat)

# 黑帽
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

cv_show("blackhat", blackhat)

礼帽:

image-20220702174230948

黑帽:

image-20220702174244339

图像梯度-Sobel算子

sobel_1

dst = cv2.Sobel(src, ddepth, dx, dy, ksize)

  • ddepth:图像的深度
  • dx和dy分别表示水平和竖直方向
  • ksize是Sobel算子的大小
1
2
3
4
img = cv2.imread("pie.png", cv2.IMREAD_GRAYSCALE)

sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)
cv_show("sobelx", sobelx)

原图像为:

image-20220702174830680

结果为:

image-20220702174756273

图像显示不全面,白到黑是正数,黑到白是负数,所有的负数都会被截断成零,所以要取绝对值

1
2
3
4
5
6
7
8
9
10
11
12
13
# 读入图像
img = cv2.imread("pie.png", cv2.IMREAD_GRAYSCALE)
cv_show("img", img)

# 梯度计算
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)
cv_show("sobelx", sobelx)

# 取绝对值
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)
sobelx = cv2.convertScaleAbs(sobelx)

cv_show("sobelx", sobelx)

结果为:

image-20220702174941117

我们上面计算的都是x方向的梯度,y方向的梯度和上面方法相似,

我们还可以计算x和y方向的图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 读入图像
img = cv2.imread("pie.png", cv2.IMREAD_GRAYSCALE)

# 梯度计算 x方向
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 3)
sobelx = cv2.convertScaleAbs(sobelx)

# 梯度计算 y方向
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("shbelxy", sobelxy)

操作结果为:

image-20220702175337091

图像梯度- Scharr算子

scharr

Scharr算子相较于Sobel算子来说差异更为明显,细节之处更能体现出来,对梯度更加敏感

图像梯度:laplacian算子

l

laplacian算子:退出一阶导,对一些变化更为敏感,但是可能受噪音影响

不同算子之间的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 不同算子之间的差异
# sobel 算子
img = cv2.imread("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)

最后结果为:

image-20220702181041818

图像的滤波操作

傅里叶变换

我们生活在时间的世界中,早上7:00起来吃早饭,8:00去挤地铁,9:00开始上班。。。以时间为参照就是时域分析。

但是在频域中一切都是静止的!

https://zhuanlan.zhihu.com/p/19763358

傅里叶变换的作用

  • 高频:变化剧烈的灰度分量,例如边界

  • 低频:变化缓慢的灰度分量,例如一片大海

滤波

  • 低通滤波器:只保留低频,会使得图像模糊

  • 高通滤波器:只保留高频,会使得图像细节增强

  • opencv中主要就是cv2.dft()和cv2.idft(),输入图像需要先转换成np.float32 格式。

  • 得到的结果中频率为0的部分会在左上角,通常要转换到中心位置,可以通过shift变换来实现。

  • cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('lena.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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
import cv2
from matplotlib import pyplot as plt

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

# 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
img = cv2.imread('lena.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

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

图像滤波可以去除噪音:

input

我们要处理的图像:

lenaNoise

有四种滤波操作:

  1. 均值滤波:简单平均的卷积操作

    1
    2
    3
    4
    5
    img = cv2.imread("lenaNoise.png")

    # blur参数:被处理图像,卷积大小
    blur = cv2.blur(img, (3, 3))
    cv_show('blur', blur)

    操作后的结果:

    image-20220702181715006

    我们可以看出虽然噪音降低了,但是还有有噪音

  2. 方框滤波:基本和均值一样,可以选择归一化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 方框滤波
    # 基本和均值一样,可以选择归一化 容易越界, 如果使用归一化的话,该方法和均值滤波一样,如果不进行归一化的,那么3*3的核内数据相加,但是没有除9的操作,这样如果超过255的话则按照255计算
    # boxFilter三个参数
    # 第一个:要处理的图像,
    # 第二个:使用-1表示处理之后的图像和原始图像颜色通道一致
    # 第三个:卷积核大小
    # 第四个: 是否进行归一化
    box = cv2.boxFilter(img, -1, (3,3), normalize = True)
    cv_show("box", box)

    box = cv2.boxFilter(img, -1, (3,3), normalize = False)
    cv_show("box", box)

    使用归一化参数:

    image-20220702182506707

    不使用归一化参数:

    image-20220702182531430

  3. 高斯滤波:高斯模糊的卷积核里的数值是满足高斯分布的,相当于更重视中间的数据

    就是中间的数值权重更大一些,有一个权重矩阵

    1
    2
    3
    aussian = cv2.GaussianBlur(img, (5,5), 1)

    cv_show("aussian", aussian)

image-20220702212020669

结果图:噪音点减少

image-20220702212228079

  1. 中值滤波:相当于用中值代替

    将数据从小到大排好序,选取中间的数据作为标准

    1
    2
    3
    median = cv2.medianBlur(img, 5)

    cv_show("median", median)

    处理结果:

    image-20220702212500446

可以看出噪音点几乎全部消失了,代价是图像变得不那么清晰

所有滤波相互比较

image-20220702213152431

图像的阈值

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_INV
  • cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取O
  • cv2.THRESH_BINARY_INV THRESH_BINARY的反转
  • cv2.THRESH_TRUNC大于阈值部分设为阈值,否则不变
  • cv2.THRESH_TOZERO大于阈值部分不改变,否则设为0
  • cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转

五种形式的展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
img = cv2.imread("cat.jpg", cv2.IMREAD_GRAYSCALE)

fig = plt.figure(figsize=(20,8), dpi = 80)

ret,thresh1 = cv2.threshold(img,127,255, cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255, cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255, cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255, cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255, cv2.THRESH_TOZERO_INV)
titles = [' original Image','BINARY','BINARY_INV','TRUNC', 'TOZERO','TOZERO_INW']
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()

结果图像:

image-20220702213457489

Canny边缘检测

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

    image-20220702213558634

  2. 梯度和方向

    image-20220702213633657

  3. 非极大值抑制

image-20220702213758340

  1. 双阈值检测

image-20220702213823943

函数调用比较简单:

直接调用Canny函数

1
2
3
4
5
6
7
8
9
10
# 双阈值检测
img = cv2.imread("lena.jpg", cv2.IMREAD_GRAYSCALE)

# 第一个参数是输入图像,需要是单通道的,即灰度图
# 第二个和的三个分别是,第一个阈值和第二个阈值
v1 = cv2.Canny(img, 80, 150)
v2 = cv2.Canny(img, 50, 100)

res = np.hstack((v1, v2))
cv_show("res", res)

结果图

image-20220702215101157

金字塔特征提取

  • 高斯金字塔

  • 拉布拉斯金字塔

    image-20220702215202326

高斯金字塔:向下采样方法(缩小)

image-20220702215301114

高斯金字塔:向上采样方法(放大)

image-20220702215312365

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导入图像
img = cv2.imread("AM.png")

cv_show("img", img)
# 输出原始图像的shape值
print(img.shape)
# (442, 340, 3)


# 使用向上采样的方法
up = cv2.pyrUp(img)
cv_show("Up", up)
# 输出改变后的图像的shape
print(up.shape)
# (884, 680, 3)


# 使用向下采样的方法
down = cv2.pyrDown(img)
cv_show("down", down)
print(down.shape)
# (221, 170, 3)

拉普拉斯金字塔:

方法:先使用低通滤波缩小原来图像的尺寸, 然后再放大尺寸,最后让原始图像减去放大之后的图像数据

image-20220702215936604

1
2
3
4
5
6
7
8
9
# 进行10次拉普拉斯方法
for i in range(10):
# 缩小尺寸
down = cv2.pyrDown(img)
# 放大尺寸
down_up = cv2.pyrUp(down)
# 数据相减
img = img - down_up
cv_show("img", img)

图像轮廓获取

contours, hierarchy = cv2.findContours(img, mode, method)

返回两个参数:

​ 第一个:各个轮廓组成的元组

​ 第二个:是轮廓的层级

mode: 轮廓检索模式

  • RETR_EXTERNAL : 质检所最外面的轮廓
  • RETR_LIST: 检索所有轮廓,并将其报错到一条链表当中
  • RETR_CCOMP: 检索所有轮廓,并将他们组织成两层:顶层是各部分的外部边界,第二层是空洞的边界
  • RETR_TREE: 检索所有的轮廓,并重构嵌套轮廓的整个层次

method:轮廓逼近方法

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

image-20220702221520712

为了使轮廓获取更加精确,建议使用二值图像

1
2
3
4
5
6
7
# 获取原始图像
img = cv2.imread('contours.png')
# 灰度处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值处理
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show("thresh", thresh)

图像轮廓的绘制

drawContours(imgae, contours, num, bgr, width)

参数:第一个 是需要在那个图像上绘制, 第二个是findContours返回的轮廓, 第三个是第几个轮廓,第四个是轮廓颜色,第五个是轮廓线条宽度

1
2
3
4
5
6
7
# 传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
# 注意,需要先将图片copy一份,这个操作会改变原来图像
draw_img = img.copy()

# 参数:第一个 是需要在那个图像上绘制, 第二个是findContours返回的轮廓, 第三个是第几个轮廓,第四个是轮廓颜色,第五个是轮廓线条宽度
res = cv2.drawContours(draw_img, contours,5, (0, 0, 255), 2)
cv_show('res',res)

原始图像 :

image-20220702222156327

轮廓图像:

image-20220702222223942

轮廓特征

我们在使用findContours函数获取到轮廓元组的时候,元组中每一个参数都代表着一个轮廓

1
2
3
4
5
6
7
8
# 获取到第0个轮廓
cnt = contours[0]

# 面积
cv2.contourArea(cnt)

# 周长, True表示闭合的
cv2.arcLength(cnt, True)

轮廓近似

当我们遇到不规则的轮廓的时候,如果想要近似称为一个比较规则的图像的时候,这时候我们就要用到轮廓近似

image-20220702222546444

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 获取原始图像
img = cv2.imread("contours2.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)
# 获取第0个轮廓
cnt = contours[0]

# 画出第0个轮廓
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0,0,255), 2)
cv_show("res", res)

image-20220702222820319

轮廓近似函数:cv2.approxPolyDP(轮廓, 精度, 是否闭合)

1
2
3
4
5
6
7
epsilon = 0.1 * cv2.arcLength(cnt, True)
# 三个参数, 第一个是我们要近似的轮廓, 第二个是阈值(一般是周长的百分比), 第三个是是否闭合
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)

image-20220702223031666

边界轮廓

我们可以指定轮廓的形状

比如说我想要这个图像的边界轮廓图像为矩形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 读取图像
img = cv2.imread("contours.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)
cv_show("img", img)

近似轮廓与轮廓的面积比

1
2
3
4
5
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/ rect_area
print("轮廓面积与边界矩形比", extent)

image-20220702223718605

外接圆形状的

1
2
3
4
5
6
# 获取中点坐标和半径
(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)

image-20220702223816577

模板匹配

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里面有6种,然后将每次计算的结果放在一个矩阵中,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出的结果矩阵是(A-a+1)x(B-b+1)

cv2.matchTemplate(img, template, method)

method方法

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

当我们使用完rec = cv2.matchTemplate() 函数后返回值是, 匹配完之后的信息

我们使用min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 获取 相关值的最小值,最大值,最小位置,最大位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 模板匹配
img = cv2.imread("lena.jpg", 0)
template = cv2.imread("face.png", 0)
# 获取模板的高和宽
h, w = template.shape[:2]

# 遍历所有方法,然后比较
methods = ["cv2.TM_SQDIFF", "cv2.TM_CCORR", "cv2.TM_CCOEFF", "cv2.TM_SQDIFF_NORMED",
"cv2.TM_CCORR_NORMED", "cv2.TM_CCOEFF_NORMED"]


for meth in methods:
# 复制图像,所有操作都在复制后这个图像上进行
img2 = img.copy()

# 匹配方法的真值
# 使用eval()函数的作用,是返回字符串所要表达的值
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()

image-20220702225729089

image-20220702225753229

image-20220702225809265

image-20220702225817635

image-20220702225830388

image-20220702225847705

匹配多个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
img_rgb = cv2.imread('test.png')
img_gray = cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)
template = cv2.imread ('test2.png', 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)

for pt in zip(*loc[::-1]):#*号表示可选参数
bottom_right = (pt[0] + w, pt[1] + h)
cv2.rectangle(img_rgb,pt,bottom_right,(0,0,255),1)
cv2.imshow('img_rgb', img_rgb)
cv2.waitKey(0)