OpencvPython正经教程

■(〇)标准模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2 as cv
import numpy as np
import os.path as pa
from matplotlib import pyplot as plt
# -----------------------------------------
res = '../../AssetResources' # pa.join(res, 'Images/')
# -----------------------------------------
img = np.zeros((300, 300), dtype=np.uint8)
img[0:img.shape[0], 0:img.shape[1]] = 255
cv.imshow('img', img)
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()
# -----------------------------------------

【模块引入】

之后都使用模块简写来代替模块全称

1
2
3
4
import cv2 as cv  # opencv本体模块
import numpy as np # 数据处理基础模块
import os.path as pa # 系统路径处理模块
from matplotlib import pyplot as plt # 绘图模块

■(一)图像处理基础框架

【图像读取】

cv.imread()【读取图像】

cv.IMREAD_UNCHANGED 或 -1 (原格式)

cv.IMREAD_GRAYSCALE 或 0 (灰度)

cv.IMREAD_COLOR 或 1 (三通道)

cv.IMREAD_ANYDEPTH 或 2 (高位图)//cv.IMREAD_ANYCOLOR 或 4 (其他颜色)

cv.IMREAD_REDUSED_COLOR_2/4/8 (三通道缩小)//cv.IMREAD_REDUSED_GRAYSCALE_2/4/8 (灰度缩小)

1
2
3
res = '../../AssetResources' # pa.join(res, 'Images/')  # 地址
img = cv.imread(pa.join(res, 'Images/zt_1.png'), cv.IMREAD_COLOR)
# 获取的img对象是np.ndarray数组

【窗口定义】

1
2
# 允许使用未定义窗口
cv.namedWindow('win_1')

【图像显示】

cv.imshow()【显示到窗口里】

1
cv.imshow('win_1', img)

【窗口等待】

cv.waitkey()【等待输入】

1
cv.waitKey(0)

【窗口返回】

cv.destroyWindow()【返回窗口】
cv.destroyAllWindows()【全部返回】

1
2
cv.destroyWindow('win')
cv.destroyAllWindows()

【图像写入】

cv.imwrite()【图像输出】

1
2
res = '../../AssetResources' # pa.join(res, 'Images/')  # 地址
cv.imwrite(pa.join(res, 'Images/zt_2.png'), img)

■(二)通道与像素

【图像信息】

1
2
3
4
5
# 图像坐标类似于二维笛卡尔坐标系第四象限坐标
# img.shape返回一个列表[row, col, cha],row和col是行数(纵)和列数(横),cha是通道数
# 在opencv中,通道数012默认为BGR而不是RGB
# img.size返回行*列*通道的大小
# img.dtype返回图像数据类型

【图像类型】

(二值图)

1
# 每个像素只有一个元素,元素除了1就是0,表示白和黑

(灰度图(8位))

1
# 每个像素只有一个元素,元素在2^0-1~2^8-1之间(0~255)

(彩色图(8位))

1
# 每个像素有三个元素,元素在2^0-1~2^8-1之间(0~255),各表示R(红)G(绿)B(蓝)所占的比例

【无中生有】

np.zeros()【生成一个元素都为0的数组】

np.ones()【生成一个元素都为1的数组】

np.random.randint()【生成一个元素随机的数组】

1
2
3
4
5
6
# 生成8位3通道5行6列数组,数组元素都为0
img0 = np.zeros((5, 6, 3), dtype=np.uint8)
# 生成8位3通道5行6列数组,数组元素都为1
img1 = np.ones((5, 6, 3), dtype=np.uint8)
# 生成8位3通道5行6列数组,数组元素都为0~256(不包含)之间的整数
img2 = np.random.randint(0, 256, size=[5, 6, 3], dtype=np.uint8)

【使用切刀】

[行数开始 : 行数结束, 列数开始 : 列数结束, 通道数开始 : 通道数结束]【表示图像中的一个部分】

[:, :, :]【表示整张图(三通道)】

[:, :]【表示整张图(灰度图)】

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
img = cv.imread(pa.join(res, 'Images/yw.png'), -1)
print(type(img))
list_b = img[:, :, 0] # 拆分蓝通道
list_g = img[:, :, 1] # 拆分绿通道
list_r = img[:, :, 2] # 拆分红通道
cv.imshow('showOri', img)
#
img[:, :, 0] = 255 - list_b
img[:, :, 1] = list_g
img[:, :, 2] = list_r
cv.imshow('showChanged', img)
cv.imwrite(pa.join(res, 'Images/output.png'), img)
# ----------------B-------------------------
blue = np.zeros((300, 300, 3), dtype=np.uint8)
blue[:, :, 0] = 255
# print('blue=\n', blue)
cv.imshow('blue', blue)
# ----------------G-------------------------
green = np.zeros((300, 300, 3), dtype=np.uint8)
green[:, :, 1] = 255
# print('green=\n', green)
cv.imshow('green', green)
# ----------------R-------------------------
red = np.zeros((300, 300, 3), dtype=np.uint8)
red[:, :, 2] = 255
# print('red=\n', red)
cv.imshow('red', red)
# ----------------BGR-------------------------
bgrImg = np.zeros((300, 300, 3), dtype=np.uint8)
bgrImg[:, 0:100, 0] = 255
bgrImg[:, 100:200, 1] = 255
bgrImg[:, 200:300, 2] = 255
bgrImg[0:100, :, 0] = 255
bgrImg[100:200, :, 1] = 255
bgrImg[200:300, :, 2] = 255
cv.imshow('bgrImg', bgrImg)
#
cv.waitKey(0)
cv.destroyAllWindows()

【访问像素】

cv.item()【读取某个像素或元素】

cv.itemset()【写入某个像素或元素】

1
2
3
4
5
img = np.random.randint(10, 200, size=[500, 500, 3], dtype=np.uint8)
# print('img=\n', img)
print(img.item(3, 2, 1)) # ndarray.item()获取值
img.itemset((3, 2, 1), 255) # ndarray.itemset()修改值
print(img.item(3, 2, 1))

【通道拆合】

cv.split()【通道拆分】

cv.merge()【通道合并】

1
2
b, g, r = cv.split(img)
ret = cv.merge([255 - g, b, r * 2]) # 传递的是列表,而不是三个对象

■(三)图像混合与位运算

【图像加法】

+

cv.add()【加法】

1
2
img2 = img1 + img1  # 超过最大数,就变成最小数
img3 = cv.add(img1, img1) # 超过最大数,就变成最大数

【图像混合】

cv.addWeighted()【加权混合】

1
2
3
4
5
6
# addWeighted(src1, alpha, src2, beta, gamma)
# src1 scr2 是两张一样大小的图片
# alpha beta 是两张图融合时所占0~1之间的比例
# gamma 是亮度增值
# addWeighted = src1 * alpha + src2 * beta + gamma
cut3 = cv.addWeighted(cut1, 0.3, cut2, 0.7, 50)

【逻辑运算】

1
2
3
4
5
6
7
8
9
10
11
12
13
# 逻辑运算
# ----------------------------------------------------------------
# a b | and or xor nand nor xnor
# ----------------------------------------------------------------
# 0 0 | 0 0 0 1 1 1
# 0 1 | 0 1 1 1 0 0
# 1 0 | 0 1 1 1 0 0
# 1 1 | 1 1 0 0 0 1
# ----------------------------------------------------------------
# 与:满足所有要求才满足
# 或:至少满足一个要求才满足
# 异或:要求状态不一样才满足,要求状态一样就不满足
# 与非/或非/同或:同上列三种相反

【按位运算】

cv.bitwise_and()【与】

cv.bitwise_or()【或】

cv.bitwise_xor()【异或】

cv.bitwise_not()【非】

1
2
3
4
5
6
7
8
9
10
11
# 十进制:198	二进制:1 1 0 0 0 1 1 0
# 十进制:219 二进制:1 1 0 1 1 0 1 1
# ----------------------------------------
# 与运算:194 二进制:1 1 0 0 0 0 1 0
# 或运算:223 二进制:1 1 0 1 1 1 1 1
# 异或算:029 二进制:0 0 0 1 1 1 0 1
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_COLOR_4)
imgMask = np.zeros(img.shape, dtype=np.uint8)
imgMask[500:850, 200:400] = 255
masked = cv.bitwise_or(img, imgMask)

【掩模(遮罩)】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
img1 = np.ones((5, 5), dtype=np.uint8) * 3
img2 = np.ones((5, 5), dtype=np.uint8) * 5
mask = np.zeros((5, 5), dtype=np.uint8)
mask[2:4, 2:4] = 1
img3 = cv.add(img1, img2, mask=mask) # 加入掩模
print(img3)
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_COLOR_4)
imgMask = np.zeros(img.shape, dtype=np.uint8)
imgMask[400:800, 400:600] = 255
imgMask[100:500, 100:400] = 255
changed = cv.bitwise_and(img, img, imgMask) # 加入掩模
for i in range(3):
changed[:, :, i] = cv.add(changed[:, :, i], 100) # 三通道同时加100
cv.imshow('img', img)
cv.imshow('imgMask', imgMask)
cv.imshow('changed', changed)
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()

【位平面分解】

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
# 将灰度图的每个像素里的值转换成八位二进制数,
# 每一位制成一张二值图,
# 提取八个位里每一位上的二值数,判断是否为1,
# 若为1,则该像素在该位平面上的显示就不是黑色
# 二进制转十进制:
# dec = a7*2^7 + a6*2^6 + a5*2^5 + a4*2^4 + a3*2^3 + a2*2^2 + a1*2^1 + a0*2^0
# a7的占比最高,对图像影响最大;a0的占比最低,对图像影响最小
# 位平面分解顺序:
img = cv.imread(pa.join(res, 'Images/zt.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_2) # 读取灰度图
cv.imshow('img', img) # 显示原图
w, h = img.shape # 原图大小:w * h
#
x = np.zeros((w, h, 8), dtype=np.uint8) # 生成8个原图大小的8位二维数组x
for i in range(8):
x[:, :, i] = 2 ** i # 每张图里的二维数组都是2的i次方,作为比较样板图
#
r = np.zeros((w, h, 8), dtype=np.uint8) # 生成8个原图大小的8位二维数组r
for i in range(8):
# r用来记录 原图 与 x的每张二次方样板图 之间的 位与运算
r[:, :, i] = cv.bitwise_and(img, x[:, :, i])
mask1 = r[:, :, i] > 0 # 记录r的每张图里不是黑色的部分,将这些部分设为白色
mask2 = r[:, :, i] <= 0 # 记录r的每张图里是黑色的部分(包含小于0的部分),将这些部分设为黑色
r[mask1] = 255
r[mask2] = 0
cv.imshow(str(i), r[:, :, i]) # 依次显示出来
cv.imwrite(str(i) + '_zt.png', r[:, :, i])
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()

【图像加密】

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
# 因为 a [异或] b = c,所以 c [异或] b = a
# 因为 图像 [异或] 钥匙 = 加密,所以 加密 [异或] 钥匙 = 图像
img = cv.imread(pa.join(res, 'Images/zt.jpg'), 0) # 读取灰度图
r, c, cha = img.shape
img_b = img[:, :, 0]
img_g = img[:, :, 1]
img_r = img[:, :, 2]
key = np.random.randint(0, 256, size=[r, c], dtype=np.uint8)
img_enc_b = cv.bitwise_xor(img_b, key)
img_enc_g = cv.bitwise_xor(img_g, key)
img_enc_r = cv.bitwise_xor(img_r, key)
img_enc = cv.merge([img_enc_b, img_enc_g, img_enc_r])
cv.imwrite(pa.join(res, 'Images/output.jpg'), img_enc)
cv.imwrite(pa.join(res, 'Images/key.jpg'), key)
# --------------------------------------------------
img_enc_b = img_enc[:, :, 0]
img_enc_g = img_enc[:, :, 1]
img_enc_r = img_enc[:, :, 2]
img_dec_b = cv.bitwise_xor(img_enc_b, key)
img_dec_g = cv.bitwise_xor(img_enc_g, key)
img_dec_r = cv.bitwise_xor(img_enc_r, key)
img_dec = cv.merge([img_dec_b, img_dec_g, img_dec_r])
cv.imshow('img', img)
cv.imshow('key', key)
cv.imshow('encryption', img_enc)
cv.imshow('decryption', img_dec)
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()

【数字水印】

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
# 将对图像影响最小的那一位平面替换成水印的二值图,就可以对图像加上数字水印
img = cv.imread(pa.join(res, 'Images/zt.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_4)
watermark = cv.imread(pa.join(res, 'Images/watermark.png'), cv.IMREAD_REDUCED_GRAYSCALE_4)
cv.imshow('img', img)
cv.imshow('watermark', watermark)
#
row_o, col_o = img.shape # 原图大小
row_w, col_w = watermark.shape # 水印大小
row_min, col_min = min(row_o, row_w), min(col_o, col_w) # 原图与水印相交大小
minWater = watermark[
int(row_w / 2 - row_min / 2):int(row_w / 2 + row_min / 2),
int(col_w / 2 - col_min / 2):int(col_w / 2 + col_min / 2)
]
resizeWater = np.zeros((row_o, col_o), dtype=np.uint8)
resizeWater[ # 符合原图大小的水印图
int(row_o / 2 - row_min / 2):int(row_o / 2 + row_min / 2),
int(col_o / 2 - col_min / 2):int(col_o / 2 + col_min / 2)
] = minWater
watermarkWhiteArea = resizeWater[:, :] > 0 # 取水印图的非黑色部分
resizeWater[watermarkWhiteArea] = 1 # 将这些部分设为二值图的白色
#
# 生成一张原图大小的全是254的灰度图:1111 1110
t254 = np.ones((row_o, col_o), dtype=np.uint8) * 254
img_bit7 = cv.bitwise_and(img, t254) # 将原图的二进制的空缺位清零
img_rebit = cv.bitwise_or(img_bit7, resizeWater) # 将原图的二进制的空缺位填充为水印图
cv.imshow('rebit', img_rebit)
#
# 生成一张原图大小的全是1的灰度图:0000 0001
t001 = np.ones((row_o, col_o), dtype=np.uint8) * 1
img_waterbit = cv.bitwise_and(img_rebit, t001) # 将水印原图的二进制的水印位剔除出来
img_waterbitWhiteArea = img_waterbit[:, :] > 0 # 取水印图的非黑色部分
img_waterbit[img_waterbitWhiteArea] = 255 # 将这些部分设为八位图的白色
cv.imshow('waterbit', img_waterbit)
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()

■(四)色彩空间

【色彩空间类型】

(RGB/BGR)

1
# R(red) # G(green) # B(blue)

(GRAY)

1
2
# Gray = 0.299*R + 0.587*G + 0.114*B
# R = Gray # G = Gray # B = Gray

(XYZ)

1
2
3
4
5
6
7
# [X]   [ 0.412453  0.357580  0.180423]   [R]
# [Y] = [ 0.212671 0.715160 0.072169] * [G]
# [Z] [ 0.019334 0.119193 0.950227] [B]
#
# [R] [ 3.240479 -1.53715 -0.498535] [X]
# [G] = [-0.969256 1.875991 0.041556] * [Y]
# [B] [ 0.055648 -0.204043 1.057311] [Z]

(HSV)

1
2
3
4
5
6
7
8
# V = max(R, G, B)
#
# V != 0, S = (V - min(R, G, B)) / V
# V == 0, S = 0
#
# V == R, H = (60 * (G - B)) / (V - min(R, G, B))
# V == G, H = 120 + (60 * (B - R)) / (V - min(R, G, B))
# V == B, H = 240 + (60 * (R - G)) / (V - min(R, G, B))

【色彩空间转换】

cv.cvtColor()【色彩空间转换】

1
2
imgtest = cv.imread(pa.join(res, 'Images/dr.jpg'), 1)
rsttest = cv.cvtColor(imgtest, cv.COLOR_BGR2HSV)

【颜色选择】

cv.inRange()【判断是否在某范围内】

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
minS, maxS = 0, 255  # 饱和度
minV, maxV = 0, 255 # 亮度
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_COLOR_4)
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
cv.imshow('img', img)
# b
minBlue = np.array([100, minS, minV])
maxBlue = np.array([150, maxS, maxV])
mask = cv.inRange(hsv, minBlue, maxBlue)
blue = cv.bitwise_and(img, img, mask=mask)
cv.imshow('blue', blue)
# g
minGreen = np.array([40, minS, minV])
maxGreen = np.array([60, maxS, maxV])
mask = cv.inRange(hsv, minGreen, maxGreen)
green = cv.bitwise_and(img, img, mask=mask)
cv.imshow('green', green)
# r
minRed = np.array([0, minS, minV])
maxRed = np.array([30, maxS, maxV])
mask = cv.inRange(hsv, minRed, maxRed)
red = cv.bitwise_and(img, img, mask=mask)
cv.imshow('red', red)
# -----------------------------------------
cv.waitKey(0)
cv.destroyAllWindows()

【Alpha透明通道】

1
2
3
4
5
img = cv.imread(pa.join(res, 'Images/zt_alpha.png'), -1)
b, g, r, a = cv.split(img)
cv.imshow('img', img)
a[:, :] = 125
bgra125 = cv.merge([b, g, r, a])

■(五)几何变换

【缩放】

cv.resize()【重置大小】

cv.INTER_NEAREST (最邻近插值)

cv.INTER_LINEAR (双线性插值)

cv.INTER_CUBIC (三次样条插值)

cv.INTER_AREA (区域插值)

cv.INTER_LINEAR_EXACT (位精确双线性插值)

1
2
3
4
5
# 缩小图像最好使用区域插值,放大图像最好使用双线性插值
img = cv.imread(pa.join(res, 'Images/zt.jpg'), -1)
row, col = img.shape[:2]
size = (int(col * 0.1), int(row * 0.1)) # 第一个是列,第二个才是行
rst = cv.resize(img, size, cv.INTER_AREA)

【翻转】

cv.flip()【翻转】

1
2
3
4
img = cv.imread(pa.join(res, 'Images/zt.jpg'), -1)
flipx = cv.flip(img, 0) # x轴翻转
flipy = cv.flip(img, 1) # y轴翻转
flipxy = cv.flip(img, -1) # xy轴翻转

【仿射(映射)】

cv.warpAffine()【仿射】

(手动矩阵构成)

1
2
3
4
5
6
7
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
height, width = img.shape[:2]
x, y = 0, 0
# M = [x1, y1, o1] [x2, y2, o2]
# move(i, j) = x1 * i + y1 * j + o1, x2 * i + y2 * j + o2
M = np.float32([[1, -0.5, x], [0, 1, y]])
move = cv.warpAffine(img, M, (width, height))

(旋转矩阵构成)

cv.getRotationMatrix2D()【带有旋转量的矩阵构成】

1
2
3
4
5
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
height, width = img.shape[:2]
# getRotationMatrix2D可以同时修改 平移旋转缩放
M = cv.getRotationMatrix2D((width / 2, height / 2), 45, 0.6)
move = cv.warpAffine(img, M, (width, height))

(平行四边形构成)

cv.getAffineTransform()【带有坐标量的矩阵构成】

1
2
3
4
5
6
7
8
9
10
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
height, width = img.shape[:2]
p1 = np.float32([[0, 0], # 左上角原始位置(带偏移量)
[height, 0], # 右上角原始位置(带偏移量)
[0, width]]) # 左下角原始位置(带偏移量)
p2 = np.float32([[0, height * 0.1], # 左上角映射位置
[width * 0.8, height * 0.25], # 右上角映射位置
[width * 0.1, height * 0.5]]) # 左下角映射位置
M = cv.getAffineTransform(p1, p2)
move = cv.warpAffine(img, M, (width, height))

【四角变幻】

cv.warpPerspective()【透视变幻】

cv.getPerspectiveTransform()【透视矩阵构成】

1
2
3
4
5
6
7
8
9
10
11
12
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
height, width = img.shape[:2]
p1 = np.float32([[0, 0], # 左上角原始位置(带偏移量)
[width, 0], # 右上角原始位置(带偏移量)
[0, height], # 左下角原始位置(带偏移量)
[width, height]]) # 右下角原始位置(带偏移量)
p2 = np.float32([[width / 5, 0], # 左上角映射位置
[width / 5 * 4, 100], # 右上角映射位置
[0, height], # 左下角映射位置
[width, height]]) # 右下角映射位置
M = cv.getPerspectiveTransform(p1, p2)
move = cv.warpPerspective(img, M, (width, height))

【逐像素映射】

remap()【逐像素映射】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
row, col = img.shape
mapx = np.zeros(img.shape, np.float32)
mapy = np.zeros(img.shape, np.float32)
for i in range(row):
for j in range(col):
mapx.itemset((i, j), j) # 修改j可影响复制图片
mapy.itemset((i, j), i) # 修改i可影响复制图片
rst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
#
#mapx = 列
#[[0 1 2 3 4]
# [0 1 2 三 4]
# [0 1 2 3 4]
# [0 1 2 3 4]]
#mapy = 行
#[[0 0 0 0 0]
# [1 1 1 一 1]
# [2 2 2 2 2]
# [3 3 3 3 3]]
# 意思是:将img中第一行第三列的元素map到第一行第三列中去
# 上述代码可将图片复制一份,没有对图片做任何修改

■(六)阈值

【阈值提取处理】

cv.threshold()【阈值提取】

cv.THRESH_BINARY (二值化处理)

cv.THRESH_BINARY_INV (反二值化处理)

cv.THRESH_TRUNC (截断处理)

cv.THRESH_TOZERO (低阈值零处理)

cv.YHRESH_TOZERO_INV (超阈值零处理)

1
2
3
4
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 0)
thresRet, rst = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)
thres0 = rst[:, :] == 0
rst[thres0] = 255

【自适应阈值处理】

cv.adaptiveThreshold()【自适应阈值处理】

cv.ADAPTIVE_THRESH_MEAN_C (权重一致自适应法)

cv.ADAPTIVE_THRESH_GAUSSIAN_C (高斯方程自适应法)

1
2
3
4
5
6
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_4)
thresRet, rst = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
rst2 = cv.adaptiveThreshold(
img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 5, 1)
rst3 = cv.adaptiveThreshold(
img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 5)

【自动匹配阈值处理】

cv.THRESH_OTSU (自动匹配阈值处理)

1
2
3
img = cv.imread(pa.join(res, 'Images/zt.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_4)
thresRet1, rst1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
thresRet2, rst2 = cv.threshold(img, 127, 255, cv.THRESH_OTSU)

■(七)图像滤波(模糊)

1
2
# 在图像上的每个像素的周围一圈像素计算加权值再传递给原图像而将原图像平滑处理的方法叫图像滤波
# 像素与周围一圈的参与像素叫做卷积核

【均值滤波】

1
2
3
# 将卷积核里所有数加在一起求平均数
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
blur = cv.blur(img, (10, 10))

【方框滤波】

1
2
3
4
5
# 将卷积核里所有数加在一起求平均数或者就仅仅是加在一起
img = cv.imread(pa.join(res, 'Images/lena.jpg'), -1)
blur = cv.boxFilter(img, -1, (10, 10))
blur1 = cv.boxFilter(img, -1, (10, 10), normalize=1)
blur2 = cv.boxFilter(img, -1, (10, 10), normalize=0)

【高斯滤波】

1
2
3
# 将卷积核里所有数按距离算权重加在一起求平均数
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
blur = cv.GaussianBlur(img, (11, 11), 6, 4) # 卷积核的值必须是奇数

【中值滤波】

1
2
3
# 将卷积核里所有数求中位数
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
blur = cv.medianBlur(img, 9) # 卷积核的值必须是奇数

【双边滤波】

1
2
3
# 将卷积核里所有数按距离按色差算权重加在一起求平均数
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
blur = cv.bilateralFilter(img, 5, 50, 50)

【自制2D卷积】

1
2
3
4
# 自制卷积核
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
kernel = np.ones((9, 9), np.float32) / (9 * 9)
blur = cv.filter2D(img, -1, kernel)

■(八)形态学

1
2
# 在二值图像上的每个像素的周围一圈像素计算异同值再拓展或收缩再传递给原图像而将原图像形状改变
# 像素与周围一圈的参与像素叫做结构元核函数

【腐蚀】

cv.erode()【腐蚀形态学】

1
2
3
4
5
6
7
8
9
10
11
12
13
img = np.zeros((5, 5), dtype=np.uint8)
img[1:4, 1:4] = 1
kernel = np.ones((3, 1), dtype=np.uint8)
ero = cv.erode(img, kernel)
print('img\n', img)
print('kernel\n', kernel)
print('ero\n', ero)
#
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((5, 5), dtype=np.uint8)
ero = cv.erode(img, kernel)
cv.imshow('img', img)
cv.imshow('ero', ero)

【膨胀】

cv.dilate()【膨胀形态学】

1
2
3
4
5
6
7
8
9
10
11
12
13
img = np.zeros((5, 5), dtype=np.uint8)
img[2:3, 1:4] = 1
kernel = np.ones((3, 1), dtype=np.uint8)
dil = cv.dilate(img, kernel)
print('img\n', img)
print('kernel\n', kernel)
print('dil\n', dil)
#
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((5, 5), dtype=np.uint8)
dil = cv.dilate(img, kernel)
cv.imshow('img', img)
cv.imshow('dil', dil)

【通用函数】

cv.morphologyEx()【形态学修改】

cv.MORPH_ERODE (腐蚀)

cv.MORPH_DILATE (膨胀)

cv.MORPH_OPEN (开运算)

cv.MORPH_CLOSE (闭运算)

cv.MORPH_GRADIENT (形态学梯度运算)

cv.MORPH_TOPHAT (礼帽运算)

cv.MORPH_BLACKHAT (黑帽运算)

【开运算】

1
2
3
4
# 先腐蚀再膨胀
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((5, 5), dtype=np.uint8)
openArg = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)

【闭运算】

1
2
3
4
# 先膨胀再腐蚀
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((5, 5), dtype=np.uint8)
closeArg = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)

【形态学梯度运算】

1
2
3
4
# 用膨胀减腐蚀
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((2, 2), dtype=np.uint8)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)

【礼帽运算】

1
2
3
4
# 用原图减开运算
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((2, 2), dtype=np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)

【黑帽运算】

1
2
3
4
# 用闭运算减原图
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
kernel = np.ones((2, 2), dtype=np.uint8)
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)

【核函数类型】

cv.getStructuringElement()【形态学核函数构造】

cv.MORPH_RECT (矩形)

cv.MORPH_CROSS (十字架)

cv.MORPH_ELLIPSE (椭圆)

1
2
3
4
5
6
7
img = cv.imread(pa.join(res, 'Images/morph.png'), -1)
k1 = cv.getStructuringElement(cv.MORPH_RECT, (50, 50))
k2 = cv.getStructuringElement(cv.MORPH_CROSS, (50, 50))
k3 = cv.getStructuringElement(cv.MORPH_ELLIPSE, (50, 50))
morph1 = cv.morphologyEx(img, cv.MORPH_DILATE, k1)
morph2 = cv.morphologyEx(img, cv.MORPH_DILATE, k2)
morph3 = cv.morphologyEx(img, cv.MORPH_DILATE, k3)

■(九)边缘梯度

1
# 图像横方向或纵方向变化的速度即图像梯度,大致称作图像边缘

【Sobel算子】

cv.Sobel()【sobel算子】

1
2
3
4
5
6
img = cv.imread(pa.join(res, 'Images/gradient.png'), 0)
sobel1 = cv.Sobel(img, cv.CV_64F, 0, 1)
sobel1 = cv.convertScaleAbs(sobel1)
sobel2 = cv.Sobel(img, cv.CV_64F, 1, 0)
sobel2 = cv.convertScaleAbs(sobel2)
sobel3 = cv.addWeighted(sobel1, 1, sobel2, 1, 0)

【Scharr算子】

cv.Scharr()【scharr算子】

1
2
3
4
5
6
img = cv.imread(pa.join(res, 'Images/gradient.png'), 0)
scharr1 = cv.Scharr(img, cv.CV_64F, 0, 1)
scharr1 = cv.convertScaleAbs(scharr1)
scharr2 = cv.Scharr(img, cv.CV_64F, 1, 0)
scharr2 = cv.convertScaleAbs(scharr2)
scharr3 = cv.addWeighted(scharr1, 1, scharr2, 1, 0)

【Laplacian算子】

cv.Laplacian()【laplacian算子】

1
2
3
img = cv.imread(pa.join(res, 'Images/gradient.png'), 0)
lap = cv.Laplacian(img, cv.CV_64F, ksize=1)
lap = cv.convertScaleAbs(lap)

■(十)边缘检测

【专业边缘检测】

cv.Canny()【canny边缘检测】

1
2
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 0)
res = cv.Canny(img, 127, 255)

■(十一)图像金字塔

【高斯金字塔】

1
# 一张图像不断向下采样,删除偶数行和列,可以得到高斯金字塔

【拉普拉斯金字塔】

1
# 一张图像先向下再向上采样,用原图与之相减而构成拉普拉斯金字塔

【图像向下采样】

1
2
3
4
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 1)
res1 = cv.pyrDown(img)
res2 = cv.pyrDown(res1)
res3 = cv.pyrDown(res2)

【图像向上采样】

1
2
3
4
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 1)
res1 = cv.pyrUp(img)
res2 = cv.pyrUp(res1)
res3 = cv.pyrUp(res2)

【拉普拉斯与恢复原图】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 1)
g0 = img
g1 = cv.pyrDown(g0)
g2 = cv.pyrDown(g1)
g3 = cv.pyrDown(g2)
l0 = g0 - cv.pyrUp(g1)
l1 = g1 - cv.pyrUp(g2)
l2 = g2 - cv.pyrUp(g3)
# 拉普拉斯图是原图与图像采样逆差图的差,用逆差图(受损图)加上拉普拉斯图就可恢复原图
img = cv.imread(pa.join(res, 'Images/lena.jpg'), 1)
g = cv.pyrDown(cv.pyrDown(img))
u = cv.pyrUp(cv.pyrUp(g))
l = img - u
#
reimg = u + l

■(十二)图像轮廓

1
# 二值图中封闭的正形叫图像轮廓

【绘制轮廓】

cv.findContours()【查找图像轮廓】

cv.drawContours【绘制图像轮廓】

cv.RETR_EXTERNAL (只检测外轮廓)

cv.RETR_LIST (检测所有轮廓,不建立等级关系)

cv.RETR_CCOMP (检测所有轮廓,建立二级关系)

cv.RETR_TREE (检测所有轮廓,建立多级关系)

cv.CHAIN_APPROX_NONE (存储所有轮廓点)

cv.CHAIN_APPROX_SIMPLE (压缩所有轮廓点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
cv.imshow('img', img)
imgg = cv.GaussianBlur(img, (11, 11), 6, 4)
# 1:手动转灰度
gray = cv.cvtColor(imgg, cv.COLOR_BGR2GRAY)
# 2:阈值处理一遍
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cv.imshow('bina', bina)
# 3:寻找轮廓
cont, hier = cv.findContours(bina, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
# 4:绘制轮廓
black = np.zeros(img.shape, dtype=np.uint8)
imgContDraw1 = cv.drawContours(black, cont, 14, (255, 255, 255), -1)
imgContDraw2 = cv.drawContours(imgg, cont, -1, (255, 255, 0), 1)
cv.imshow('imgContDraw1', imgContDraw1)
cv.imshow('imgContDraw2', imgContDraw2)
# 5:抠图
cut = cv.bitwise_and(img, imgContDraw1)
cv.imshow('cut', cut)

【矩特征】

(moments矩集)

cv.moments()【轮廓的矩】

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
# --空间矩
# 零阶矩 m00
# 一阶矩 m10 m01
# 二阶矩 m20 m11 m02
# 三阶矩 m30 m21 m12 m03
# --中心矩
# 二阶中心矩 mu20 mu11 mu02
# 三阶中心矩 mu30 mu21 mu12 mu03
# --归一化中心矩
# 二阶Hu矩 nu20 nu11 nu02
# 三阶Hu矩 nu30 nu21 nu12 nu03
#
img = cv.imread(pa.join(res, 'Images/Contours/contours.png'), cv.IMREAD_REDUCED_COLOR_2)
cv.imshow('img', img)
# 1:手动转灰度
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 2:阈值处理一遍
ret, bina = cv.threshold(gray, 254, 255, cv.THRESH_BINARY)
cv.imshow('bina', bina)
# 3:寻找轮廓
cont, hier = cv.findContours(bina, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
n = len(cont)
contImg = []
for i in range(n):
temp = np.zeros(gray.shape, dtype=np.uint8)
contImg.append(temp)
contImg[i] = cv.drawContours(contImg[i], cont, i, 255, 1)
cv.imshow(f'cont{i}', contImg[i])
for i in range(n):
print(f'矩{i}\n', cv.moments(cont[i]))
for i in range(n):
print(f'面积{i}\n', cv.moments(cont[i])['m00'])

(轮廓面积)

cv.contourArea()【轮廓面积】

1
2
3
4
5
6
7
8
9
10
11
...
cont, hier = cv.findContours(bina, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
n = len(cont)
contImg = []
for i in range(n):
print(f'cont面积{i}=', cv.contourArea(cont[i])) # 面积
temp = np.zeros(gray.shape, dtype=np.uint8)
contImg.append(temp)
if cv.contourArea(cont[i]) > 3000: # 只显示面积大过3000平方像素的轮廓
contImg[i] = cv.drawContours(contImg[i], cont, i, 255, 1)
cv.imshow(f'cont{i}', contImg[i])

(轮廓周长)

cv.arcLength()【轮廓周长】

1
2
3
4
5
6
7
8
9
10
...
cont, hier = cv.findContours(bina, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
n = len(cont)
contImg = []
for i in range(n):
print(f'cont长度{i}=', cv.arcLength(cont[i], True)) # 周长
temp = np.zeros(gray.shape, dtype=np.uint8)
contImg.append(temp)
contImg[i] = cv.drawContours(contImg[i], cont, i, 255, 1)
cv.imshow(f'cont{i}', contImg[i])

(Hu归一化中心矩)

cv.HuMoments()【Hu矩】

1
2
3
4
5
6
7
8
# --归一化中心矩
# 二阶Hu矩 nu20 nu11 nu02
# 三阶Hu矩 nu30 nu21 nu12 nu03
#
img = cv.imread(pa.join(res, 'Images/Contours/contours.png'), cv.IMREAD_REDUCED_COLOR_2)
cv.imshow('img', img)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
print(cv.HuMoments(cv.moments(gray)))

(轮廓匹配)

cv.matchShapes()【轮廓匹配】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 导入图像
img1 = cv.imread(pa.join(res, 'Images/Contours/cont1.png'), cv.IMREAD_REDUCED_COLOR_2)
img2 = cv.imread(pa.join(res, 'Images/Contours/cont2.png'), cv.IMREAD_REDUCED_COLOR_2)
img3 = cv.imread(pa.join(res, 'Images/Contours/cont3.png'), cv.IMREAD_REDUCED_COLOR_2)
# 转灰度信息
gray1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
gray2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
gray3 = cv.cvtColor(img3, cv.COLOR_BGR2GRAY)
# 二值化阈值
ret1, bin1 = cv.threshold(gray1, 127, 255, cv.THRESH_BINARY)
ret2, bin2 = cv.threshold(gray2, 127, 255, cv.THRESH_BINARY)
ret3, bin3 = cv.threshold(gray3, 127, 255, cv.THRESH_BINARY)
# 获取轮廓
cont1, hier1 = cv.findContours(bin1, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
cont2, hier2 = cv.findContours(bin2, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
cont3, hier3 = cv.findContours(bin3, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
# 将 提取检测到的第0个轮廓 进行匹配对比
ret0 = cv.matchShapes(cont1[0], cont1[0], 1, 0) # 自己和自己匹配 0.00 无差距
ret1 = cv.matchShapes(cont1[0], cont2[0], 1, 0) # 和第二张图匹配 0.07 差距较小
ret2 = cv.matchShapes(cont1[0], cont3[0], 1, 0) # 和第三张图匹配 0.19 差距较大
#
print(ret0)
print(ret1)
print(ret2)

【形状匹配】

(正矩形包围框)

cv.boundingRect()【正矩形包围框】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
img = cv.imread(pa.join(res, 'Images/Contours/cont1.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv.boundingRect(cont[0]) # 四个返回值:左上角的坐标xy,矩形的长w和宽h
print(x, y, w, h)
# 可以使用四点构造绘图
arr = np.array([[x, y], [x + w, y], [x + w, y + h], [x, y + h]])
cv.drawContours(img, [arr], -1, (0, 0, 255), 10)
# 可以使用矩形构造绘图
cv.rectangle(img, (x, y), (x + w, y + h), (255, 255, 0), 2)
cv.imshow('img', img)
# 也可以返回一个元组
rect = cv.boundingRect(cont[0])
print(rect)

(最小矩形包围框)

cv.minAreaRect()【最小矩形包围框】

cv.boxPoints()【点集构成】

1
2
3
4
5
6
7
8
9
img = cv.imread(pa.join(res, 'Images/Contours/cont1.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
rect = cv.minAreaRect(cont[0])
points = cv.boxPoints(rect)
points = np.int0(points) # 将浮点取整
img1 = cv.drawContours(img, [points], -1, (0, 0, 255), 10)
cv.imshow('img1', img1)

(最小圆包围框)

cv.minEnclosingCircle()【最小圆包围框】

1
2
3
4
5
6
7
img = cv.imread(pa.join(res, 'Images/Contours/cont1.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
(x, y), radius = cv.minEnclosingCircle(cont[0])
cv.circle(img, (int(x), int(y)), int(radius), (255, 255, 0), 2)
cv.imshow('img', img)

(最优椭圆包围框)

cv.fitEllipse()【最优椭圆包围框】

1
2
3
4
5
6
7
img = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
ellipse = cv.fitEllipse(cont[0])
cv.ellipse(img, ellipse, (255, 255, 0), 2)
cv.imshow('img', img)

(最优直线导向)

cv.fitLine()【最优直线导向】

1
2
3
4
5
6
7
8
9
10
img = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
row, col = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cont[0], cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((col - x) * vy / vx) + y)
cv.line(img, (col - 1, righty), (0, lefty), (255, 255, 0), 2)
cv.imshow('img', img)

(最小三角形包围框)

cv.minEnclosingTriangle()【最小三角形包围框】

1
2
3
4
5
6
7
8
img = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
area, tran = cv.minEnclosingTriangle(cont[0])
for i in range(0, 3):
cv.line(img, tuple(tran[i][0]), tuple(tran[(i + 1) % 3][0]), (255, 255, 0), 2)
cv.imshow('img', img)

(相似多边形)

cv.approxPolyDP()【相似多边形】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cont, hier = cv.findContours(bina, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
# 精度为 0.1 * 周长
adp = img.copy()
epsilon = 0.1 * cv.arcLength(cont[0], True)
approx = cv.approxPolyDP(cont[0], epsilon, True)
adp = cv.drawContours(adp, [approx], -1, (0, 0, 255), 2)
cv.imshow('0.1', adp)
# 精度为 0.02 * 周长
adp = img.copy()
epsilon = 0.02 * cv.arcLength(cont[0], True)
approx = cv.approxPolyDP(cont[0], epsilon, True)
adp = cv.drawContours(adp, [approx], -1, (0, 0, 255), 2)
cv.imshow('0.05', adp)
# 精度为 0.005 * 周长
adp = img.copy()
epsilon = 0.005 * cv.arcLength(cont[0], True)
approx = cv.approxPolyDP(cont[0], epsilon, True)
adp = cv.drawContours(adp, [approx], -1, (0, 0, 255), 2)
cv.imshow('0.01', adp)

【凸包】

(凸包)

cv.convexHull()【获得凸包】

1
2
hull = cv.convexHull(cont[0])
cv.polylines(img, [hull], True, (0, 255, 0), 2)

(凸缺陷)

cv.convexityDefects()【获得凸缺陷】

1
2
3
4
5
6
7
8
9
hull = cv.convexHull(cont[0], returnPoints=False)
defects = cv.convexityDefects(cont[0], hull)
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(cont[0][s][0])
end = tuple(cont[0][e][0])
far = tuple(cont[0][f][0])
cv.line(img, start, end, [0, 0, 255], 2)
cv.circle(img, far, 5, [255, 0, 0], -1)

(是否凸)

cv.isContourConvex()【判断形状是否凸】

1
2
3
4
5
6
7
8
9
10
11
12
img1 = img.copy()
hull = cv.convexHull(cont[0]) # 构造凸包
cv.polylines(img1, [hull], True, (0, 255, 0), 2)
cv.imshow('img1', img1)
print('img1\n', cv.isContourConvex(hull))
#
img2 = img.copy()
epsilon = 0.01 * cv.arcLength(cont[0], True)
approx = cv.approxPolyDP(cont[0], epsilon, True) # 构造逼近四边形
img2 = cv.drawContours(img2, [approx], 0, (0, 0, 255), 2)
cv.imshow('img2', img2)
print('img2\n', cv.isContourConvex(approx))

(点与轮廓)

cv.pointPolygonTest()【点到轮廓的距离和点是否在轮廓内】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hull = cv.convexHull(cont[0])  # 构造凸包
cv.polylines(img, [hull], True, (0, 255, 0), 2)
#
dista = cv.pointPolygonTest(hull, (300, 150), True) # True代表输出点到轮廓的距离
distaf = cv.pointPolygonTest(hull, (300, 150), False) # False代表输出点是否在轮廓内
cv.putText(img, 'A', (300, 150), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
print('distA', dista)
print('distAf', distaf)
#
distb = cv.pointPolygonTest(hull, (423, 112), True) # True代表输出点到轮廓的距离
distbf = cv.pointPolygonTest(hull, (423, 112), False) # False代表输出点是否在轮廓内
cv.putText(img, 'B', (423, 112), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
print('distB', distb)
print('distBf', distbf)

【形状场景距离】

cv.createShapeContextDistanceExtractor()【标准形状场景距离算子构造】

cv.createHausdorffDistanceExtractor()【Hausdorff场景距离算子构造】

cv.createShapeContextDistanceExtractor().computeDistance()【标准计算两个形状的距离】

cv.createHausdorffDistanceExtractor().computeDistance()【Hausdorff计算两个形状的距离】

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
img1 = cv.imread(pa.join(res, 'Images/Contours/cont1.png'))
gray1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
ret1, bina1 = cv.threshold(gray1, 127, 255, cv.THRESH_BINARY)
cont1, hier1 = cv.findContours(bina1, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
#
img2 = cv.imread(pa.join(res, 'Images/Contours/cont2.png'))
gray2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
ret2, bina2 = cv.threshold(gray2, 127, 255, cv.THRESH_BINARY)
cont2, hier2 = cv.findContours(bina2, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
#
img3 = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray3 = cv.cvtColor(img3, cv.COLOR_BGR2GRAY)
ret3, bina3 = cv.threshold(gray3, 127, 255, cv.THRESH_BINARY)
cont3, hier3 = cv.findContours(bina3, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
#
sd = cv.createShapeContextDistanceExtractor()
#
d1 = sd.computeDistance(cont1[0], cont1[0]) # 场景距离为0, 图像没有差距
d2 = sd.computeDistance(cont1[0], cont2[0]) # 场景距离为9, 图像没有较大
d3 = sd.computeDistance(cont1[0], cont3[0]) # 场景距离为4, 图像没有较小
# -------------------------------------------------------------------------
img1 = cv.imread(pa.join(res, 'Images/Contours/cont1.png'))
img2 = cv.imread(pa.join(res, 'Images/Contours/cont2.png'))
img3 = cv.imread(pa.join(res, 'Images/Contours/cont3.png'))
gray1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
gray2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
gray3 = cv.cvtColor(img3, cv.COLOR_BGR2GRAY)
ret1, bina1 = cv.threshold(gray1, 127, 255, cv.THRESH_BINARY)
ret2, bina2 = cv.threshold(gray2, 127, 255, cv.THRESH_BINARY)
ret3, bina3 = cv.threshold(gray3, 127, 255, cv.THRESH_BINARY)
cont1, hier1 = cv.findContours(bina1, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
cont2, hier2 = cv.findContours(bina2, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
cont3, hier3 = cv.findContours(bina3, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
#
hd = cv.createHausdorffDistanceExtractor()
#
d1 = hd.computeDistance(cont1[0], cont1[0]) # Hausdorff距离为0, 图像没有差距
d2 = hd.computeDistance(cont1[0], cont2[0]) # Hausdorff距离为48, 图像差距大
d3 = hd.computeDistance(cont1[0], cont3[0]) # Hausdorff距离为39, 图像差距小

【轮廓特殊值】

np.nonzero()【找出数组中非零元素的位置】

np.transpose()【将两个数组移项】

cv.findNonZero()【找出数组中非零元素的索引】

cv.minMaxLoc()【最大值最小值及其位置】

cv.mean()【平均颜色平均灰度】

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
# 正矩形框位置和长宽
x, y, w, h = cv.boundingRect(cont[0])
# 宽高比
aspectRatio = float(w) / h
# 轮廓面积/正矩形框面积 延展度
extend = cv.contourArea(cont[0]) / (w * h)
# 轮廓面积/凸包面积 凸包度
solidity = cv.contourArea(cont[0]) / cv.contourArea(cv.convexHull(cont[0]))
# 与轮廓面积相等的圆的直径 等效直径
equivalent = np.sqrt((4 * cv.contourArea(cont[0])) / np.pi)
# 最优椭圆与势方向(椭圆中心点)(椭圆水平轴与垂直轴长)角度
(ellipsex, ellipsey), (horiLen, vertLen), angle = cv.fitEllipse(cont[0])
# 使用numpy获取轮廓像素点
mask1 = np.zeros(gray, dtype=np.uint8)
trans1 = np.transpose(np.nonzero(mask1))
# 使用cv.findNonZero()获取轮廓像素点
trans2 = cv.findNonZero(mask1)
# 寻找最大值和最小值
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(gray, mask=mask1)
# 平均颜色与平均灰度
meanVal = cv.mean(img, mask=mask1)
# 图像位置极点
leftmost = tuple(cont[0][cont[0][:, :, 0].argmin()][0])
rightmost = tuple(cont[0][cont[0][:, :, 0].argmax()][0])
topmost = tuple(cont[0][cont[0][:, :, 1].argmin()][0])
bottommost = tuple(cont[0][cont[0][:, :, 1].argmax()][0])

■(十三)直方图

1
# 将图像位数梯度(0~255)设为x轴,像素在该通道每个梯度出现的次数或比例设为y轴,而制成直方图

【绘制直方图】

matplotlib.pyplot.hist()【mat模块直方图绘制】

cv.calcHist【获得直方图信息】

matplotlib.pyplot.plot()【mat模块绘制直方图信息】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
# color='rgbwcymk'
plt.hist(img.ravel(), 256, color='y')
#
histb = cv.calcHist([img], [0], None, [256], [0, 255])
histg = cv.calcHist([img], [1], None, [256], [0, 255])
histr = cv.calcHist([img], [2], None, [256], [0, 255])
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')
plt.plot(histb + histg + histr)
#
plt.show()

【直方图加遮罩】

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/lena.jpg'))
mask = np.zeros(img.shape[:2], dtype=np.uint8)
mask[int(mask.shape[0]/2 - 100):int(mask.shape[0]/2 + 100), int(mask.shape[1]/2 - 100):int(mask.shape[1]/2 + 100)] = 255
#
histb = cv.calcHist([img], [0], None, [256], [0, 255])
histbm = cv.calcHist([img], [0], mask, [256], [0, 255])
plt.plot(histb, color='b')
plt.plot(histbm, color='g')
#
plt.show()

【图像均衡化】

cv.equalizeHist()【图像均衡化】

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_4)
equ = cv.equalizeHist(img)
plt.figure('img')
plt.hist(img.ravel(), 256)
plt.figure('equ')
plt.hist(equ.ravel(), 256)
#
plt.show()

【matplotlib的绘图介绍】

matplotlib.pyplot.figure()【开启一个pyplot窗口】

matplotlib.pyplot.subplot()【在pyplot窗口里添加子对象】

matplotlib.pyplot.show()【显示pyplot窗口】

1
2
3
4
5
6
7
8
9
10
11
import matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), cv.IMREAD_REDUCED_GRAYSCALE_4)
equ = cv.equalizeHist(img)
plt.figure('img') # 开启一个pyplot窗口
# pyplot窗口显示:subplot(几行,几列,第几个位置),显示的内容
plt.subplot(1, 4, 1), plt.hist(img.ravel(), 256)
plt.subplot(1, 4, 2), plt.hist(equ.ravel(), 256)
plt.subplot(1, 4, 3), plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.subplot(1, 4, 4), plt.imshow(cv.cvtColor(equ, cv.COLOR_BGR2RGB))
plt.show() # pyplot显示

■(十四)傅里叶变换

1
2
3
# 将图像转成正弦余弦波->即傅里叶变换
# 将正弦余弦波转成图像->即逆傅里叶变换
# 在傅里叶变换与逆傅里叶变换中,将正弦余弦波修改->即图像的高低通处理

【FFT傅里叶变换】

numpy.fft.fft2()【傅里叶变换】

numpy.fft.fftshift()【矫正傅里叶变换】

numpy.fft.ifft2()【逆傅里叶变换】

numpy.fft.ifftshift()【逆矫正傅里叶变换】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), 0)
# 傅里叶变换
f = np.fft.fft2(img) # 傅里叶变换生成一个复数数组
fshift = np.fft.fftshift(f) # 将零频率成分移动到图像的中心位置
# 逆傅里叶变换
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
# 显示原图
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.title('original'), plt.axis('off')
# 显示傅里叶频谱图
spectrum = 20 * np.log(np.abs(fshift))
plt.subplot(132), plt.imshow(spectrum, cmap='gray'), plt.title('result'), plt.axis('off')
# 显示逆傅里叶图像
iimg = np.abs(iimg)
plt.subplot(133), plt.imshow(iimg, cmap='gray'), plt.title('iimg'), plt.axis('off')
#
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 matplotlib.pyplot as plt
#
img = cv.imread(pa.join(res, 'Images/dr.jpg'), 0)
# 傅里叶变换
f = np.fft.fft2(img) # 傅里叶变换生成一个复数数组
fshift = np.fft.fftshift(f) # 将零频率成分移动到图像的中心位置
# 计算高低通
r, c = img.shape # 原始图像尺寸
rh, ch = int(r/2), int(c/2) # 图像一半尺寸
deg = 2 # 遮罩大小比例
# xoff, yoff = 248.5, 351 # 遮罩偏移
xoff, yoff = 0, 0 # 遮罩偏移
mask1 = np.ones(img.shape, dtype=np.uint8) # 低通
mask1[int(rh - rh/deg + yoff):int(rh + rh/deg + yoff), int(ch - ch/deg + xoff):int(ch + ch/deg + xoff)] = 0
mask2 = np.zeros(img.shape, dtype=np.uint8) # 高通
mask2[int(rh - rh/deg + yoff):int(rh + rh/deg + yoff), int(ch - ch/deg + xoff):int(ch + ch/deg + xoff)] = 1
# fshift = fshift * mask1 # 在这里乘上mask1(低通/显示模糊)或者mask2(高通/显示边缘)
# 逆傅里叶变换
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
# 显示原图
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.title('original'), plt.axis('off')
# 显示傅里叶频谱图
spectrum = 20 * np.log(np.abs(fshift) + 10**-5)
plt.subplot(132), plt.imshow(spectrum, cmap='gray'), plt.title('result'), plt.axis('off')
# 显示逆傅里叶图像
iimg = np.abs(iimg)
plt.subplot(133), plt.imshow(iimg, cmap='gray'), plt.title('iimg'), plt.axis('off')
#
plt.show()

■(十五)模板匹配

1
# 将模板图在扫描图里扫描,记录相似比对比例,即模板匹配

【单模板单匹配结构】

cv.matchTemplate()【模板匹配】

1
2
3
4
5
6
7
8
9
10
11
12
13
img = cv.imread(pa.join(res, 'Images/dr.jpg'), 1)
temp = cv.imread(pa.join(res, 'Images/Template/dr_t1.png'), 1)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
temp = cv.cvtColor(temp, cv.COLOR_BGR2RGB)
rv = cv.matchTemplate(img, temp, cv.TM_SQDIFF) # 模板匹配
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(rv) # 求极值
print('minVal\n', minVal)
print('maxVal\n', maxVal)
#
th, tw = temp.shape[:2] # 模板图大小
topLeft = minLoc # 方框左上角
bottomRight = (topLeft[0] + tw, topLeft[1] + th) # 方框右下角
cv.rectangle(img, topLeft, bottomRight, 255, 2) # 在匹配位置画方框

【单模板多匹配结构】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
img = cv.imread(pa.join(res, 'Images/Template/dr_t3.png'), 1)
temp = cv.imread(pa.join(res, 'Images/Template/dr_t1.png'), 1)
temp = cv.GaussianBlur(temp, (1, 1), 6, 4)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
temp = cv.cvtColor(temp, cv.COLOR_BGR2RGB)
res = cv.matchTemplate(img, temp, cv.TM_CCOEFF_NORMED) # 模板匹配返回0~1之间的匹配值
#
h, w = temp.shape[:2] # 模板图大小
threshold = 0.9 # 阈值设定值0~1
loc = np.where(res >= threshold) # 在模板匹配中,满足大于等于阈值的值的坐标y和x
#
# loc里只有两个数y和x,用[::-1]转成x和y,并用*将列表转化成zip的位置实参
for pos in zip(*loc[::-1]): # pos记录匹配到的图像的左上角坐标(x, y)
cv.rectangle(img, pos, (pos[0] + w, pos[1] + h), 255, 1)

■(十六)霍夫变换

1
# 在图像里找直线即霍夫变换

【霍夫变换】

cv.HoughLines()【霍夫直线】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
img = cv.imread(pa.join(res, 'Images/fractal.png'), 1)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
edge = cv.Canny(gray, 200, 255, apertureSize=3)
#
houghs = cv.HoughLines(edge, 1, np.pi/180, 300)
blank = np.zeros(img.shape, dtype=np.uint8) # 在一张黑色画布上显示
try:
for h in houghs:
r, t = h[0]
cost, sint = np.cos(t), np.sin(t)
x0, y0 = cost * r, sint * r # 直线中心坐标
x1, y1 = int(x0 + 10 ** 4 * (-sint)), int(y0 + 10 ** 4 * cost) # 直线起始坐标
x2, y2 = int(x0 - 10 ** 4 * (-sint)), int(y0 - 10 ** 4 * cost) # 直线终点坐标
cv.line(blank, (x1, y1), (x2, y2), (0, 255, 0), 1)
except TypeError:
print('no Number')
#
plt.subplot(121), plt.imshow(edge, cmap='gray'), plt.axis('off')
plt.subplot(122), plt.imshow(blank, cmap='gray'), plt.axis('off')
plt.show()

【概率霍夫变换】

cv.HoughLinesP()【概率霍夫直线】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
img = cv.imread(pa.join(res, 'Images/fractal.png'), 1)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
edge = cv.Canny(gray, 50, 150, apertureSize=3)
#
# cv.HoughLinesP(接受检测的八位二值图,距离r精度,角度θ精度,线检测阈值,接受直线最小长度,一条线中两个点的最大间隔)
houghs = cv.HoughLinesP(edge, 1, np.pi/180, 1, minLineLength=img.shape[0]/10, maxLineGap=20)
blank = np.zeros(img.shape, dtype=np.uint8) # 在一张黑色画布上显示
try:
for h in houghs:
x1, y1, x2, y2 = h[0]
cv.line(blank, (x1, y1), (x2, y2), (255, 255, 0), 1)
except TypeError:
print('no Number')
#
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.axis('off')
plt.subplot(132), plt.imshow(edge, cmap='gray'), plt.axis('off')
plt.subplot(133), plt.imshow(blank, cmap='gray'), plt.axis('off')
plt.show()

【霍夫圆环变换】

cv.HoughCircles()【霍夫圆环】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = cv.imread(pa.join(res, 'Images/dr.jpg'), 1)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
gray = cv.medianBlur(gray, 5)
#
# ruler = int((img.shape[0] * img.shape[1]) / 50000) # 取一个与图像分辨率相关的大小用来适应不同分辨率图像
ruler = max(img.shape[0], img.shape[1]) / 50
print(ruler)
# cv.HoughCircles(接受检测的八位灰度图,模式,累计器分辨率默认1,两个圆心间的最小间距,
# Canny高阈值(低阈值为其一半),圆的数量阈值,圆半径最小值,圆半径最大值)
houghs = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, int(ruler),
param1=127, param2=30, minRadius=int(ruler * 1), maxRadius=int(ruler * 3))
try: # 尝试获取houghs里的[[[圆坐标x, 圆坐标y, 圆半径r]]]
for i in houghs[0, :]:
cv.circle(img, (int(i[0]), int(i[1])), int(i[2]), (255, 255, 0), 1)
cv.circle(img, (int(i[0]), int(i[1])), 2, (0, 255, 255), 2)
except TypeError: # 如果找不到
print('no Number')
#
plt.subplot(121), plt.imshow(gray, cmap='gray'), plt.axis('off')
plt.subplot(122), plt.imshow(img, cmap='gray'), plt.axis('off')
plt.show()

■(十七)图像提取

1
# 就是抠图而已

【前景与背景】

cv.distanceTransform()【计算二值图中白色到黑色的距离】

cv.DIST_USER (自定义距离)

cv.DIST_L2 (欧几里得距离)

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
img = cv.imread(pa.join(res, 'Images/zt.jpg'), 1)
imgrgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(imgrgb, cv.COLOR_RGB2GRAY)
#
ret, thresh = cv.threshold(gray, 150, 255, cv.THRESH_BINARY) # 灰度图的阈值 + cv.THRESH_OTSU
kernel = np.ones((3, 3), dtype=np.uint8) # 形态卷积核
openex = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2) # 开运算去噪
#
# backward
backward = cv.dilate(openex, kernel, iterations=3) # 膨胀
# dist
dist = cv.distanceTransform(openex, cv.DIST_L2, 5) # 距离 3或5
# forward
ret2, forward = cv.threshold(dist, 0.1 * dist.max(), 255, 0) # 距离图的阈值
forward = np.uint8(forward) # 将float32转为uint8
# 不确定范围
un = cv.subtract(backward, forward)
# marker
retMark, markers = cv.connectedComponents(forward)
markers = markers + 1 # 将标注整体提亮1
markers[un == 255] = 0 # 将不确定范围标注为0
#
plt.subplot(241), plt.imshow(imgrgb, cmap='gray'), plt.axis('off'), plt.title('RGB/Original')
plt.subplot(242), plt.imshow(openex, cmap='gray'), plt.axis('off'), plt.title('Threshold/Open')
plt.subplot(243), plt.imshow(backward, cmap='gray'), plt.axis('off'), plt.title('BackGround/Dilate')
plt.subplot(244), plt.imshow(dist, cmap='gray'), plt.axis('off'), plt.title('Distance/Open')
plt.subplot(245), plt.imshow(forward, cmap='gray'), plt.axis('off'), plt.title('Distance/Threshold')
plt.subplot(246), plt.imshow(markers, cmap='gray'), plt.axis('off'), plt.title('markers')
plt.subplot(247), plt.imshow(un, cmap='gray'), plt.axis('off'), plt.title('Uncertain')
plt.show()

【分水岭算法】

cv.connectedComponents()【二值图标注相连部分为同一色阶】

cv.watershed()【分水岭算法】

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
img = cv.imread(pa.join(res, 'Images/zt.jpg'), 1)
imgrgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(imgrgb, cv.COLOR_RGB2GRAY)
#
ret, thresh = cv.threshold(gray, 150, 255, cv.THRESH_BINARY) # 灰度图的阈值 + cv.THRESH_OTSU
kernel = np.ones((3, 3), dtype=np.uint8) # 形态卷积核
openex = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2) # 开运算去噪
#
# backward背景
backward = cv.dilate(openex, kernel, iterations=3) # 膨胀
# dist距离
dist = cv.distanceTransform(openex, cv.DIST_L2, 5) # 距离
# forward前景
ret2, forward = cv.threshold(dist, 0.1 * dist.max(), 255, 0) # 距离图的阈值
forward = np.uint8(forward) # 将float32转为uint8
# unknown背景-前景
unknown = cv.subtract(backward, forward)
# marker前景标注
retMark, markers = cv.connectedComponents(forward)
markers = markers + 1 # 将标注整体提亮1
markers[unknown == 255] = 0 # 将不确定范围标注为0
# watershed分水岭
imgws = imgrgb.copy()
waterShed = cv.watershed(imgws, markers.copy())
imgws[waterShed == -1] = [255, 255, 0]
#
plt.subplot(241), plt.imshow(imgrgb, cmap='gray'), plt.axis('off'), plt.title('RGB/Original')
plt.subplot(242), plt.imshow(openex, cmap='gray'), plt.axis('off'), plt.title('Threshold/Open')
plt.subplot(243), plt.imshow(backward, cmap='gray'), plt.axis('off'), plt.title('Backward/Dilate')
plt.subplot(244), plt.imshow(dist, cmap='gray'), plt.axis('off'), plt.title('Distance/Open')
plt.subplot(245), plt.imshow(forward, cmap='gray'), plt.axis('off'), plt.title('Forward/Threshold')
plt.subplot(246), plt.imshow(unknown, cmap='gray'), plt.axis('off'), plt.title('Backward - Forward')
plt.subplot(247), plt.imshow(markers, cmap='gray'), plt.axis('off'), plt.title('markers')
plt.subplot(248), plt.imshow(imgws, cmap='gray'), plt.axis('off'), plt.title('WaterShed')
plt.show()

【方框前景提取】

cv.grabCut()【前景提取】

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
img = cv.imread(pa.join(res, 'Images/yw.png'), 1)
imgrgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
#
mask = np.zeros(img.shape[:2], dtype=np.uint8) # 与原图等大的遮罩
#
bgModel = np.zeros((1, 65), dtype=np.float64) # 1行64列的64位数组
fgModel = np.zeros((1, 65), dtype=np.float64) # 1行64列的64位数组
#
border = 1 # 矩形框边界宽
rectBorder = (border, border, img.shape[1]-border*2, img.shape[0]-border*2) # 矩形框x y w h
# grabcut运算
mask, bg, fg = cv.grabCut(img, mask, rectBorder, bgModel, fgModel, 10, cv.GC_INIT_WITH_RECT)
#
# mask在grabcut运算之后,成为拥有0 1 2三个数的三值图
# mask中将 框内背景(2) 和 框外背景(0) 设为0,将余下的 框内前景(1) 设为1
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8') # 将int32转为uint8
cut = img * mask2[:, :, np.newaxis] # 将原图按遮罩内容(前景)扣下来
#
cut = cv.cvtColor(cut, cv.COLOR_BGR2RGB)
plt.subplot(141), plt.imshow(imgrgb)
plt.subplot(142), plt.imshow(mask, cmap='gray')
plt.subplot(143), plt.imshow(mask2, cmap='gray')
plt.subplot(144), plt.imshow(cut)
#
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
31
32
img = cv.imread(pa.join(res, 'Images/yw.png'), 1)
imgrgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
#
mask = np.zeros(img.shape[:2], dtype=np.uint8) # 与原图等大的遮罩
#
bgModel = np.zeros((1, 65), dtype=np.float64) # 1行64列的64位数组
fgModel = np.zeros((1, 65), dtype=np.float64) # 1行64列的64位数组
#
border = 1 # 矩形框边界宽
rectBorder = (border, border, img.shape[1]-border*2, img.shape[0]-border*2) # 矩形框x y w h
# 第一次grabcut运算
mask, bg1, fg1 = cv.grabCut(img, mask, rectBorder, bgModel, fgModel, 10, cv.GC_INIT_WITH_RECT)
# 将交互图里黑色白色取出,手动设置在遮罩图上
maskcut = cv.imread(pa.join(res, 'Images/yw_cut.png'), 0)
mask[maskcut == 0] = 0
mask[maskcut == 255] = 1
# 第二次grabcut运算
mask, bg2, fg2 = cv.grabCut(img, mask, None, bgModel, fgModel, 10, cv.GC_INIT_WITH_MASK)
#
# mask在grabcut运算之后,成为拥有0 1 2三个数的三值图
# mask中将 框内背景(2) 和 框外背景(0) 设为0,将余下的 框内前景(1) 设为1
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8') # 将int32转为uint8
cut = img * mask2[:, :, np.newaxis] # 将原图按遮罩内容(前景)扣下来
#
cut = cv.cvtColor(cut, cv.COLOR_BGR2RGB)
plt.subplot(241), plt.imshow(imgrgb)
plt.subplot(242), plt.imshow(maskcut, cmap='gray')
plt.subplot(243), plt.imshow(mask, cmap='gray')
plt.subplot(244), plt.imshow(mask2, cmap='gray')
plt.subplot(245), plt.imshow(cut)
#
plt.show()

■(十八)视频处理

【主要函数】

cv.VideoCapture()【视频类构造】

cv.VideoCapture.isOpened【初始化是否成功】

cv.VideoCapture.open()【打开视频】

cv.VideoCapture.read()【读取视频信息】

cv.VideoCapture.release()【释放视频类】

cv.VideoCapture.get()【获取属性】

cv.VideoCapture.set()【设置属性】

cv.VideoCapture.grab()【指向下一帧】

cv.VideoCapture.retrieve()【解码本帧】

cv.VideoWriter()【视频写入类构造】

cv.VideoWriter_fourcc()【指定视频编码格式】

cv.VideoWriter.write()【写入下一帧视频】

cv.VideoWriter.release()【释放视频写入类】

【视频处理模板】

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
import cv2 as cv
import numpy as np
import os.path as pa
import matplotlib.pyplot as plt
# -----------------------------------------
res = '../../AssetResources' # pa.join(res, 'Videos/')
# -----------------------------------------
cap = cv.VideoCapture(pa.join(res, 'Videos/video1.mp4')) # pa.join(res, 'Videos/video1.mp4')
capResolution = (int(cap.get(3)), int(cap.get(4))) # 3->width 4->height
#
resolution = capResolution
fps = 30
cap.set(3, resolution[0]), cap.set(4, resolution[1])
fourcc = cv.VideoWriter_fourcc(*'DIVX') # MJPG-.avi/DIVX-.mp4/FLV1-.flv/8BPS-.mov
output = cv.VideoWriter(pa.join(res, 'Videos/video2.mp4'), fourcc, 30, resolution, isColor=True)
# -----------------------------------------
while cap.isOpened():
ret, frame = cap.read()
if ret:
frame = cv.flip(frame, 0)
# ---------------------------------
cv.imshow('frame', frame)
output.write(frame)
# ---------------------------------
c = cv.waitKey(fps) # fps
if c == 32: # 空格结束
break
else:
break
# -----------------------------------------
cap.release()
output.release()
# -----------------------------------------
cv.waitKey(1)
cv.destroyAllWindows()
# -----------------------------------------

【摄像头处理模板】

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
import cv2 as cv
import numpy as np
import os.path as pa
import matplotlib.pyplot as plt
# -----------------------------------------
res = '../../AssetResources' # pa.join(res, 'Videos/')
# -----------------------------------------
cap = cv.VideoCapture(0) # pa.join(res, 'Videos/video1.mp4')
capResolution = (int(cap.get(3)), int(cap.get(4))) # 3->width 4->height
#
resolution = capResolution # 3->width 4->height
cap.set(3, resolution[0]), cap.set(4, resolution[1])
fourcc = cv.VideoWriter_fourcc(*'DIVX') # MJPG-.avi/DIVX-.mp4/FLV1-.flv/8BPS-.mov
output = cv.VideoWriter(pa.join(res, 'Videos/video2.mp4'), fourcc, 30, resolution, isColor=True)
# -----------------------------------------
while cap.isOpened():
ret, frame = cap.read()
if ret:
#
# ---------------------------------
cv.imshow('frame', frame)
output.write(frame)
# ---------------------------------
c = cv.waitKey(1)
if c == 32: # 空格结束
break
else:
break
# -----------------------------------------
cap.release()
output.release()
# -----------------------------------------
cv.waitKey(1000)
cv.destroyAllWindows()
# -----------------------------------------

■(十九)绘图交互

【绘图】

cv.line()【直线】

cv.rectangle()【矩形】

cv.circle()【圆形】

cv.ellipse()【椭圆】

cv.polylines()【多边形】

cv.putText()【文字】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
canvas = np.zeros((500, 500, 3), dtype=np.uint8)
row, col = canvas.shape[:2]
# 直线
cv.line(canvas, (0, 250), (500, 250), (0, 0, 255), 1, cv.LINE_AA)
cv.line(canvas, (250, 0), (250, 500), (0, 0, 255), 1, cv.LINE_AA)
# 矩形
cv.rectangle(canvas, (125, 125), (375, 375), (0, 0, 255), 1, cv.LINE_AA)
# 圆形
cv.circle(canvas, (250, 250), 125, (0, 0, 255), 1, cv.LINE_AA)
# 椭圆
cv.ellipse(canvas, (250, 250), (250, 125), 90, 0, 360, (0, 0, 255), 1, cv.LINE_AA)
# 多边形
points = np.array([[[250, 0]], [[500, 250]], [[250, 500]], [[0, 250]]], dtype=np.int32)
cv.polylines(canvas, [points], True, (0, 0, 255), 1, cv.LINE_AA)
# 文字
cv.putText(canvas, 'HELLOWORLD', (250, 250), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1)
#
cv.imshow('canvas', canvas)

【鼠标与键】

cv.setMouseCallback()【鼠标方法与窗口映射】

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
42
43
44
45
46
# 左中右键
# EVENT_FLAG_LBUTTON = 1
# EVENT_LBUTTONDOWN = 1
# EVENT_LBUTTONUP = 4
# EVENT_LBUTTONDBLCLK = 7
#
# EVENT_FLAG_RBUTTON = 2
# EVENT_RBUTTONDOWN = 2
# EVENT_RBUTTONUP = 5
# EVENT_RBUTTONDBLCLK = 8
#
# EVENT_FLAG_MBUTTON = 4
# EVENT_MBUTTONDOWN = 3
# EVENT_MBUTTONUP = 6
# EVENT_MBUTTONDBLCLK = 9
#
# EVENT_MOUSEMOVE = 0
# EVENT_MOUSEWHEEL = 10
# EVENT_MOUSEHWHEEL = 11
#
# 附属键
# EVENT_FLAG_CTRLKEY = 8
# EVENT_FLAG_SHIFTKEY = 16
# EVENT_FLAG_ALTKEY = 32
#
# -----------------------------------------
startPoint = (0, 0)
endPoint = (0, 0)
def mouse_action(event, x, y, flags, param):
global startPoint, endPoint
if event == cv.EVENT_LBUTTONDOWN:
startPoint = (x, y)
elif event == cv.EVENT_LBUTTONUP:
endPoint = (x, y)
cv.rectangle(img, startPoint, endPoint, (0, 0, 255), 1, cv.LINE_AA)
#
img = np.full((500, 500, 3), fill_value=0, dtype=np.uint8)
cv.namedWindow('win1')
cv.setMouseCallback('win1', mouse_action)
while True:
cv.imshow('win1', img)
if cv.waitKey(20) == 32:
break
# -----------------------------------------
cv.destroyAllWindows()
# -----------------------------------------

【滑动条】

cv.createTrackbar()【滑动条创建】

cv.getTrackbarPos()【滑动条调用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def change_color(pos):
# cv.getTrackbarPos(滚动条名,窗口名)
r = cv.getTrackbarPos('R', 'img')
g = cv.getTrackbarPos('G', 'img')
b = cv.getTrackbarPos('B', 'img')
# 调用滑动条
img[:, :, :] = [b, g, r]
# 窗口
img = np.zeros((100, 700, 3), dtype=np.uint8)
cv.namedWindow('img')
# cv.createTrackbar(滚动条名,窗口名,范围最小值,范围最大值,调用函数)
cv.createTrackbar('R', 'img', 0, 255, change_color)
cv.createTrackbar('G', 'img', 0, 255, change_color)
cv.createTrackbar('B', 'img', 0, 255, change_color)
#
while True: # 窗口刷新
cv.imshow('img', img)
if cv.waitKey(1) == 32:
break
# -----------------------------------------
cv.destroyAllWindows()
# -----------------------------------------