opencv官网文档学习

文章最后有一些图片资源

1.图像处理基本使用

import cv2

# 读取图像
image = cv2.imread("images/1.png", cv2.IMREAD_GRAYSCALE)
print("image:",image)

# 显示图像
namedWindow = cv2.namedWindow("images/1.png")
cv2.imshow("images/1.png", image)

# 等待按键
waitKey = cv2.waitKey(0)  # 值为0、负数的时候,表示无限等待
print("waitKey:",waitKey)  # 返回该按键的 ASCII 码
# Python 提供了函数 ord(),用来获取字符的 ASCII 码值
if waitKey == ord('A') or waitKey == ord('a'):
    print("you press the A or a")

# 销毁窗口
destroyWindow = cv2.destroyWindow("images/1.png")
print("destroyWindow:",destroyWindow)  # None

image = cv2.imread("images/1.png", cv2.IMREAD_COLOR)
print("image.shape:",image.shape)
# 行和列坐标来访问像素值
px=image[100,100]
print("px=",px)
# 仅访问蓝色像素
blue=image[100,100,0]
print("blue=",blue)

# 销毁所有窗口
cv2.destroyAllWindows()

# 保存图像
imwrite = cv2.imwrite("output/1_output.png", image)
print(imwrite)  # 如果没有output的目录则会返回False,存储失败

2.图像处理基础

import cv2
import numpy as np
import matplotlib.pyplot as plt

# numpy.array 访问像素


# 随机生成一张彩色图像,并用itemset可以修改某一个像素值
img = np.random.randint(0, 256, size=[500, 500, 3], dtype=np.uint8)
print("img=\n", img)
print("读取像素点img.item(3,2)=", img.item(3, 2, 1))
img.itemset((3, 2, 1), 255)
print("修改后img=\n", img)
print("修改后像素点img.item(3,2)=", img.item(3, 2, 1))
cv2.imshow("hh", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 感兴趣区域ROI 在设定感兴趣区域 ROI 后,就可以对该区域进行整体操作。
import cv2
a=cv2.imread("images/1.png",cv2.IMREAD_UNCHANGED)
print(a.shape)
face=a[60:160,60:160]
cv2.imshow("original",a)
cv2.imshow("face",face)
cv2.waitKey()
cv2.destroyAllWindows()

# 通道拆分
red_img=a[:,:,2]
cv2.imshow("red_img",red_img)
a[:,:,2]=0
cv2.imshow("nored_img", a)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 通过函数拆分 split
b,g,r=cv2.split(a)
plt.figure()
plt.subplot(1,3,1)
plt.imshow(b)
plt.title("b", fontsize=8)
plt.xticks([])
plt.yticks([])
plt.subplot(1,3,2)
plt.imshow(g)
plt.title("g",fontsize=8)
plt.xticks([])
plt.yticks([])
plt.subplot(1,3,3)
plt.imshow(r)
plt.title("r",fontsize=8)
plt.xticks([])
plt.yticks([])
plt.show()

# 通道合并  merge
bgr=cv2.merge([b,g,r])
rgb=cv2.merge([r,g,b])
cv2.imshow("image",a)
cv2.imshow("bgr",bgr)
cv2.imshow("rgb",rgb)
cv2.waitKey()
cv2.destroyAllWindows()



# 为图像设置边框(填充)
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1=cv.imread("images/zlh.jpg",cv.IMREAD_COLOR)
replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'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()

3.图像运算

import cv2
import numpy as np
import matplotlib.pyplot as plt

# # 读取图像
image = cv2.imread("images/1.png")
# print(image)

# 加法运算
r = np.random.randint(0, 255, (500, 500, 1), dtype=np.uint8)
g = np.random.randint(0, 255, (500, 500, 1), dtype=np.uint8)
b = np.random.randint(0, 255, (500, 500, 1), dtype=np.uint8)
# 𝑎 + 𝑏 = mod(𝑎 + 𝑏, 256)
res = r + g + b
cv2.imshow("b=g+r+a", res)
# a 和像素值 b 进行求和运算时,会得到像素值对应图像的饱和值(最大值) 超过255即为255
res = cv2.add(g, r, b)
cv2.imshow("b=cv2.add(g,r,a)", res)
cv2.waitKey(0)

merge = cv2.merge([b, g, r])
cv2.imshow("merge", merge)
cv2.waitKey(0)

# 图像加权和
# 计算两幅图像的像素值之和时,将每幅图像的权重考虑进来
# 这个可以实现两幅图像的重叠
# dst = src1×alpha + src2×beta + gamma
addWeighted = cv2.addWeighted(r, 10, g, 3, 10)
cv2.imshow("addWeighted", addWeighted)
cv2.waitKey(0)

# 按位逻辑运算
# 按位与运算 dst = cv2.bitwise_and( src1, src2[, mask]] )
mask = np.zeros(image.shape, dtype=np.uint8)
mask[60:120, 10:70] = 255
mask[160:190, 80:190] = 255
bitwise_and = cv2.bitwise_and(image, mask)
cv2.imshow("bitwise_and", bitwise_and)
cv2.waitKey(0)
# 按位或运算 dst = cv2.bitwise_or( src1, src2[, mask]] )
# 按位非运算 dst = cv2.bitwise_not( src[, mask]] )
# 按位异或运算 dst = cv2.bitwise_xor( src1, src2[, mask]] )

# 掩模
img1 = np.ones((4, 4), dtype=np.uint8) * 3
img2 = np.ones((4, 4), dtype=np.uint8) * 5
mask = np.zeros((4, 4), dtype=np.uint8)
mask[2:4, 2:4] = 1
img3 = np.ones((4, 4), dtype=np.uint8) * 66
print("img1=\n", img1)
print("img2=\n", img2)
print("mask=\n", mask)
print("初始值img3=\n", img3)
# 在计算时,掩码为 1 的部分对应“img1+img2”,其他部分的像素值均为“0”
img3 = cv2.add(img1, img2, mask=mask)
print("求和后img3=\n", img3)

# 图像与数值的运算
# 如果想增加图像的整体亮度,可以将每一个像素值都加上一个特定值。
img1 = np.ones((4, 4), dtype=np.uint8) * 3
img2 = np.ones((4, 4), dtype=np.uint8) * 5
print("img1=\n", img1)
print("img2=\n", img2)
img3 = cv2.add(img1, img2)
print("cv2.add(img1,img2)=\n", img3)
img4 = cv2.add(img1, 6)
print("cv2.add(img1,6)\n", img4)

# 位平面分解
# 将灰度图像中处于同一比特位上的二进制像素值进行组合,得到一幅二进制值图像,该图像被称为灰度图像的一个位平面,这个过程被称为位平面分解
# 针对 RGB 图像,如果将 R 通道、G 通道、B 通道中的每一个通道对应的位平面进行合并,即可组成新的 RGB 彩色图像。
r, c = image.shape
# 构造提取矩阵,
x = np.zeros((r, c, 8), dtype=np.uint8)  # r 是行高,c 是列宽,8 表示共有 8 个通道.矩阵 x 的 8 个通道分别用来提取灰度图像的 8 个位平面
for i in range(8):
    x[:, :, i] = 2 ** i  # 设置用于提取各个位平面的提取矩阵的值
r = np.zeros((r, c, 8), dtype=np.uint8)
for i in range(8):
    r[:, :, i] = cv2.bitwise_and(image, x[:, :, i])
    mask = r[:, :, i] > 0  # 阈值处理
    r[mask] = 255  # 每次提取位平面后,要想让二值位平面能够以黑白颜色显示出来,就要将得到的二值位平面进行阈值处理,将其中大于零的值处理为 255
    cv2.imshow(str(i), r[:, :, i])
    cv2.waitKey(0)

# 按位运算练习
# 加载两张图片
img1 = cv2.imread('images/zlh.jpg')
img2 = cv2.imread('images/opencv-logo.png')
# 我想把logo放在左上角,所以我创建了ROI
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
print("roi:", roi)
# 现在创建logo的掩码,并同时创建其相反掩码
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) #在目标检测和图像合成过程中,通常使用灰度图像来创建掩码,因为灰度图像只有一个通道,更容易处理。
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY) # 这一行使用阈值处理,将 img2gray 中的像素分为两个类别。像素值小于 10 的被设置为 0(黑色),大于等于 10 的被设置为 255(白色)。这就创建了一个二进制掩码 mask,其中白色表示要覆盖的区域,黑色表示不覆盖的区域。
'''
threshold(src,thresh,maxval,type) type:指定阈值处理的类型。常见的类型包括:
    cv2.THRESH_BINARY:像素值大于阈值的设为 maxval,否则设为0。
    cv2.THRESH_BINARY_INV:像素值大于阈值的设为0,否则设为 maxval。
    cv2.THRESH_TRUNC:像素值大于阈值的设为阈值,否则不变。
    cv2.THRESH_TOZERO:像素值大于阈值的不变,否则设为0。
    cv2.THRESH_TOZERO_INV:像素值大于阈值的设为0,否则不变。
'''
mask_inv = cv2.bitwise_not(mask) #将 mask 中的黑色区域变成白色,白色区域变成黑色.可以使用 mask_inv 来确定哪些部分不被覆盖,哪些部分保持原样。
# 现在将ROI中logo的区域涂黑
img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
# 仅从logo图像中提取logo区域
img2_fg = cv2.bitwise_and(img2, img2, mask=mask)
# 将logo放入ROI并修改主图像
dst = cv2.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst

plt.subplot(231),plt.imshow(mask,'gray'),plt.title('mask')
plt.subplot(232),plt.imshow(mask_inv,'gray'),plt.title('mask_inv')
plt.subplot(233),plt.imshow(img1_bg,'gray'),plt.title('img1_bg')
plt.subplot(234),plt.imshow(img2_fg,'gray'),plt.title('img2_fg')
plt.subplot(235),plt.imshow(dst,'gray'),plt.title('dst')
plt.subplot(236),plt.imshow(img1,'gray'),plt.title('res')
plt.show()

# 图像的加密和解密
# 通过对原始图像与密钥图像进行按位异或,可以实现加密;将加密后的图像与密钥图像再次进行按位异或,可以实现解密。
r, c, channel = image.shape
key = np.random.randint(0, 256, size=[r, c, channel], dtype=np.uint8)
encryption = cv2.bitwise_xor(image, key)
decryption = cv2.bitwise_xor(encryption, key)
cv2.imshow("image", image)
cv2.imshow("key", key)
cv2.imshow("encryption", encryption)
cv2.imshow("decryption", decryption)
cv2.waitKey(0)

# 数字水印
# 	嵌入过程:将载体图像的第 0 个位平面替换为数字水印信息(一幅二值图像)。
# 	提取过程:将载体图像的最低有效位所构成的第 0 个位平面提取出来,得到数字水印信息。
# 读取原始载体图像
lena = cv2.imread("images/lena.bmp", 0)
# 读取水印图像
watermark = cv2.imread("images/watermark.bmp", 0)
# 将水印内的255处理为1,以方便嵌入
# 后续章节会介绍使用threshold处理。
w = watermark[:, :] > 0
watermark[w] = 1
# 读取原始载体图像的shape值
r, c = lena.shape
# ============嵌入过程============
# 生成内部值都是254的数组
t254 = np.ones((r, c), dtype=np.uint8) * 254
# 获取lena图像的高7位
lenaH7 = cv2.bitwise_and(lena, t254)
# 将watermark嵌入到lenaH7内
e = cv2.bitwise_or(lenaH7, watermark)
# ============提取过程============
# 生成内部值都是1的数组
t1 = np.ones((r, c), dtype=np.uint8)
# 从载体图像内,提取水印图像
wm = cv2.bitwise_and(e, t1)
print(wm)
# 将水印内的1处理为255以方便显示
# 后续章节会介绍threshold实现。
w = wm[:, :] > 0
wm[w] = 255
# ============显示============
cv2.imshow("lena", lena)
cv2.imshow("watermark", watermark * 255)  # 当前watermark内最大值为1
cv2.imshow("e", e)
cv2.imshow("wm", wm)
cv2.waitKey()

# 脸部打码及解码
lena = cv2.imread("images/lena.bmp", 0)
# 读取原始载体图像的shape值
r, c = lena.shape
mask = np.zeros((r, c), dtype=np.uint8)
mask[220:400, 250:350] = 1
# 获取一个key,打码、解码所使用的密钥
key = np.random.randint(0, 256, size=[r, c], dtype=np.uint8)
# ============获取打码脸============
# 使用密钥key对原始图像lena加密
lenaXorKey = cv2.bitwise_xor(lena, key)
# 获取加密图像的脸部信息encryptFace
encryptFace = cv2.bitwise_and(lenaXorKey, mask * 255)
# 将图像lena内的脸部值设置为0,得到noFace1
noFace1 = cv2.bitwise_and(lena, (1 - mask) * 255)
# 得到打码的lena图像
maskFace = encryptFace + noFace1
# ============将打码脸解码============
# 将脸部打码的lena与密钥key进行异或运算,得到脸部的原始信息
extractOriginal = cv2.bitwise_xor(maskFace, key)
# 将解码的脸部信息extractOriginal提取出来,得到extractFace
extractFace = cv2.bitwise_and(extractOriginal, mask * 255)
# 从脸部打码的lena内提取没有脸部信息的lena图像,得到noFace2
noFace2 = cv2.bitwise_and(maskFace, (1 - mask) * 255)
# 得到解码的lena图像
extractLena = noFace2 + extractFace
# ============显示图像============
cv2.imshow("lena", lena)
cv2.imshow("mask", mask * 255)
cv2.imshow("1-mask", (1 - mask) * 255)
cv2.imshow("key", key)
cv2.imshow("lenaXorKey", lenaXorKey)
cv2.imshow("encryptFace", encryptFace)
cv2.imshow("noFace1", noFace1)
cv2.imshow("maskFace", maskFace)
cv2.imshow("extractOriginal", extractOriginal)
cv2.imshow("extractFace", extractFace)
cv2.imshow("noFace2", noFace2)
cv2.imshow("extractLena", extractLena)
cv2.waitKey()

cv2.destroyAllWindows()

4.色彩空间类型转换

import cv2

# 读取图像
image = cv2.imread("images/1.png", cv2.IMREAD_GRAYSCALE)
# print(image)

# 显示图像
namedWindow = cv2.namedWindow("images/1.png")
cv2.imshow("images/1.png",image)

# 等待按键
waitKey=cv2.waitKey(0) #值为0、负数的时候,表示无限等待
print(waitKey) #返回该按键的 ASCII 码
# Python 提供了函数 ord(),用来获取字符的 ASCII 码值
if waitKey ==ord('A') or waitKey ==ord('a'):
    print("you press the A or a")

# 销毁窗口
destroyWindow=cv2.destroyWindow("images/1.png")
print(destroyWindow) #None
# 销毁所有窗口
cv2.destroyAllWindows()


# 保存图像
imwrite=cv2.imwrite("output/1_output.png",image)
print(imwrite) # 如果没有output的目录则会返回False,存储失败


5.OpenCV中的图像处理

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 设置matplotlib.pyplot显示中文字体
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置字体
plt.rcParams["axes.unicode_minus"] = False  # 该语句解决图像中的“-”负号的乱码问题

# ——————对于颜色转换,我们使用cv函数。————————
# cvtColor(input_image, flag),其中flag决定转换的类型。
# 对于BGR→灰度转换,我们使用标志cv.COLOR_BGR2GRAY。类似地,对于BGR→HSV,我们使用标志cv.COLOR_BGR2HSV。
# 要获取其他标记,只需在Python终端中运行以下命令:
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)

# ——————对象追踪——————
# 将BGR图像转换成HSV,我们可以使用它来提取一个有颜色的对象
cap = cv.VideoCapture(0)  # 获取电脑自带的相机
while (1):
    # 读取帧
    _, frame = cap.read()
    # 转换颜色空间 BGR 到 HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    # 定义HSV中蓝色的范围
    lower_blue = np.array([110, 50, 50])
    upper_blue = np.array([130, 255, 255])
    # 设置HSV的阈值以提取蓝色
    blue_mask = cv.inRange(hsv, lower_blue, upper_blue)

    # 定义HSV中红色的范围
    lower_red1 = np.array([0, 50, 50])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([160, 50, 50])
    upper_red2 = np.array([180, 255, 255])
    # 设置HSV的阈值以提取红色
    red_mask1 = cv.inRange(hsv, lower_red1, upper_red1)
    red_mask2 = cv.inRange(hsv, lower_red2, upper_red2)
    red_mask = cv.bitwise_or(red_mask1, red_mask2)

    # 定义HSV中绿色的范围
    lower_green = np.array([40, 50, 50])
    upper_green = np.array([80, 255, 255])
    # 设置HSV的阈值以提取绿色
    green_mask = cv.inRange(hsv, lower_green, upper_green)

    # 合并不同颜色区域的掩码
    final_mask = cv.bitwise_or(blue_mask, cv.bitwise_or(red_mask, green_mask))

    # 将掩膜和图像逐像素相加
    extracted_image = cv.bitwise_and(frame, frame, mask=final_mask)

    cv.imshow('frame', frame)
    cv.imshow('mask', final_mask)
    cv.imshow('res', extracted_image)
    k = cv.waitKey(5) & 0xFF
    if k == 27:
        break
cv.destroyAllWindows()

# 要查找绿色的HSV值,请在Python终端中尝试以下命令:
green = np.uint8([[[0, 255, 0]]])
hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
print(hsv_green)

# —————— 图像的几何变换 ——————
# 缩放
img = cv.imread('images/zlh.jpg')
res = cv.resize(img, None, fx=0.2, fy=0.2, interpolation=cv.INTER_CUBIC)
# 或者
height, width = img.shape[:2]
res2 = cv.resize(img, (1 * width, 1 * height), interpolation=cv.INTER_CUBIC)  # 这个的数组中要求是整数
cv.imshow('img', img)
cv.imshow('res', res)
cv.imshow('res2', res2)
cv.waitKey(0)

cv.destroyAllWindows()

# 平移
img = cv.imread('images/zlh.jpg')
rows, cols, _ = img.shape
# 在(x,y)方向上的位移,则将其设为(tx,ty),你可以创建转换矩阵M
# M=[[1,0,tx],[0,1,ty]]
M = np.float32([[1, 0, 100], [0, 1, 50]])
res = cv.warpAffine(img, M, (cols, rows))  # 第三个参数(width,height),width=列数cols,height=行数rows。
cv.imshow('img', res)
cv.waitKey(0)
cv.destroyAllWindows()

# 旋转
img = cv.imread('images/zlh.jpg')
rows, cols, _ = img.shape
# cols-1 和 rows-1 是坐标限制
M = cv.getRotationMatrix2D(((cols - 1) / 2.0, (rows - 1) / 2.0), 90,
                           1)  # cv.getRotationMatrix2D((旋转中心x坐标,旋转中心y坐标),旋转角度,缩放因子):提供了可缩放的旋转以及可调整的旋转中心
dst = cv.warpAffine(img, M, (cols, rows))  # cv.warpAffine(要旋转的图片,旋转矩阵M,(cols, rows)输出图像的大小):这个函数用于应用仿射变换矩阵,实际执行图像旋转。
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()

# 仿射变换
# 在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。
# 为了找到变换矩阵,我们需要输入图像中的三个点及其在输出图像中的对应位置。然后**cv.getAffineTransform**将创建一个2x3矩阵,该矩阵将传递给**cv.warpAffine**。
img = cv.imread('images/zlh.jpg')
rows, cols, ch = img.shape
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv.getAffineTransform(pts1, pts2)  # 两组点的坐标,分别代表了仿射变换的源点和目标点。
dst = cv.warpAffine(img, M, (cols, rows))
plt.figure("放射变换")
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

# 透视变换
# 对于透视变换,您需要3x3变换矩阵。即使在转换后,直线也将保持直线。
# 要找到此变换矩阵,您需要在输入图像上有4个点,在输出图像上需要相应的点。在这四个点中,其中三个不应共线。
# 然后可以通过函数**cv.getPerspectiveTransform**找到变换矩阵。然后将**cv.warpPerspective**应用于此3x3转换矩阵。
img = cv.imread('images/zlh.jpg')
rows, cols, ch = img.shape
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv.getPerspectiveTransform(pts1, pts2)
dst = cv.warpPerspective(img, M, (300, 300))
plt.figure("透视变换")
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

# —————— 图像阈值 ——————
# 简单阈值
img = cv.imread('images/gradient.png', 0)  # 0表示灰度图像
# cv.threshold(灰度图像,用于进行分类的阈值,分配给超过阈值的像素值的最大值,阈值类型):如果像素值小于阈值,则将其设置为0,否则将其设置为最大值。
ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
plt.figure("简单阈值")
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 = cv.imread('images/shudu.png', 0)  # 0表示灰度图像
blockSize = 11  # blockSize 指定用于计算每个像素阈值的邻域大小
C = 2  # C是从计算的局部平均值中减去的常数,可用于微调阈值
MEAN = cv.adaptiveThreshold(image, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, blockSize, C)
GAUSSIAN = cv.adaptiveThreshold(image, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, blockSize, C)
_, THRESH_BINARY = cv.threshold(image, 127, 255, cv.THRESH_BINARY)

# adaptiveThreshold参数:
#   src:源图像 灰度
#   maxValue:表示当像素值高于或等于阈值时,要赋予的新值。通常设置为 255,表示白色。
#   adaptiveMethod:自适应阈值的计算方法,有两种选择:
#       cv.ADAPTIVE_THRESH_MEAN_C:基于邻域块的平均值计算阈值。
#       cv.ADAPTIVE_THRESH_GAUSSIAN_C:基于邻域块的加权平均值(高斯加权)计算阈值。
#   thresholdType:阈值类型,用于指定阈值计算后的二值化类型。通常设置为 cv.THRESH_BINARY(将像素值高于阈值的设为 maxValue,否则为0)或 cv.THRESH_BINARY_INV(与 cv.THRESH_BINARY 相反)。
#   blockSize:表示用于计算局部阈值的邻域块的大小。通常是一个奇数值,例如11,15,21等。它决定了局部块的大小。
#   C:是从局部块的平均值中减去的常数,可用于微调阈值。通常是正数,通常在0到10之间。较大的 C 值将导致更多像素被分类为前景。
#   dst:输出图像,即包含自适应阈值处理结果的图像。
# 显示原始图像和自适应阈值分割后的图像
plt.subplot(221), plt.imshow(image, "gray"), plt.title('image')
plt.subplot(222), plt.imshow(MEAN, "gray"), plt.title('MEAN')
plt.subplot(223), plt.imshow(GAUSSIAN, "gray"), plt.title('GAUSSIAN')
plt.subplot(224), plt.imshow(THRESH_BINARY, "gray"), plt.title('THRESH_BINARY')
plt.show()
# 等待用户关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()

# Otsu的二值化
# 在全局阈值化中,我们使用任意选择的值作为阈值。相反,Otsu的方法避免了必须选择一个值并自动确定它的情况。
# 考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。类似地,Otsu的方法从图像直方图中确定最佳全局阈值。
# 示例:输入图像为噪点图像。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果。
img = cv.imread('images/noisy.png', 0)
rows, cols = img.shape
# 将图像高斯噪声处理
noise = np.random.normal(0, 25, (rows, cols)).astype(np.uint8)
img = cv.add(img, noise)

# 全局阈值
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# Otsu阈值
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 绘制所有图像及其直方图
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
          'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
          'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
for i in range(3):
    plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
    plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
    plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
    plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 图像平滑 ——————
# 2D卷积(图像过滤)
# 与一维信号一样,还可以使用各种低通滤波器(LPF),高通滤波器(HPF)等对图像进行滤波。LPF有助于消除噪声,使图像模糊等。HPF滤波器有助于在图像中找到边缘。
# 示例:保持这个内核在一个像素上,将所有低于这个内核的25个像素相加,取其平均值,然后用新的平均值替换中心像素。
img = cv.imread('images/opencv-logo.png')
kernel = np.ones((5, 5), np.float32) / 25
dst = cv.filter2D(img, -1, kernel)
plt.figure("2D卷积(图像过滤)")
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(dst), plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

# 图像模糊(图像平滑)
# 通过将图像与低通滤波器内核进行卷积来实现图像模糊。这对于消除噪音很有用。它实际上从图像中消除了高频部分(例如噪声,边缘)。因此,在此操作中边缘有些模糊。
img = cv.imread('images/opencv-logo.png')
rows, cols, ch = img.shape
# 将图像高斯噪声处理
noise = np.random.normal(0, 25, (rows, cols, ch)).astype(np.uint8)
img = cv.add(img, noise)
# OpenCV主要提供四种类型的模糊技术:
# (1)平均:这是通过将图像与归一化框滤镜进行卷积来完成的。它仅获取内核区域下所有像素的平均值,并替换中心元素。这是通过功能**cv.blur()或**cv.boxFilter()完成的。
blur = cv.blur(img, (5, 5))  # 内核大小5*5
# (2)高斯模糊:内核的宽度和高度,该宽度和高度应为正数和奇数。我们还应指定X和Y方向的标准偏差,分别为sigmaX和sigmaY。如果仅指定sigmaX,则将sigmaY与sigmaX相同。如果两个都为零,则根据内核大小进行计算。
GaussianBlur = cv.GaussianBlur(img, (5, 5), 0)  # 内核大小5*5
# (3)中位模糊:函数**cv.medianBlur()** 提取内核区域下所有像素的中值,并将中心元素替换为该中值。这对于消除图像中的椒盐噪声非常有效。
medianBlur = cv.medianBlur(img, 5)
# (4)双边滤波:cv.bilateralFilter() 在去除噪声的同时保持边缘清晰锐利非常有效。空间的高斯函数确保仅考虑附近像素的模糊,而强度差的高斯函数确保仅考虑强度与中心像素相似的那些像素的模糊。由于边缘的像素强度变化较大,因此可以保留边缘。
# bilateralFilter参数:
#   src:源图像,要进行滤波处理的输入图像。通常是单通道或多通道的灰度图像或彩色图像。
#   d:表示滤波器的空间距离,即像素之间的空间距离。它是一个正数,通常在1到10之间。较大的值表示在更广泛的空间范围内考虑像素的相似性。
#   sigmaColor:表示颜色相似性高斯函数的标准差,即像素值之间的相似性。它通常是一个正数,在1到200之间。较大的值表示对像素值的差异性较小。
#   sigmaSpace:表示空间相似性高斯函数的标准差,即像素之间的空间距离。它通常是一个正数,在1到200之间。较大的值表示对空间距离的差异性较小。
#   dst:输出图像,即经过双边滤波处理后的图像。通常与输入图像具有相同的尺寸和通道数。
#   borderType:用于边界扩展的参数。通常设置为 cv.BORDER_DEFAULT。
bilateralFilter = cv.bilateralFilter(img, 9, 150, 150)

plt.subplot(231), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(232), plt.imshow(blur), plt.title('blur')
plt.xticks([]), plt.yticks([])
plt.subplot(233), plt.imshow(GaussianBlur), plt.title('GaussianBlur')
plt.xticks([]), plt.yticks([])
plt.subplot(234), plt.imshow(medianBlur), plt.title('medianBlur')
plt.xticks([]), plt.yticks([])
plt.subplot(235), plt.imshow(bilateralFilter), plt.title('bilateralFilter')
plt.xticks([]), plt.yticks([])
plt.show()

# —————— 形态转化 ——————
img = cv.imread('images/j.png', 0)
kernel = np.ones((5, 5), np.uint8)
# 侵蚀
# 原始图像中的一个像素(无论是1还是0)只有当内核下的所有像素都是1时才被认为是1,否则它就会被侵蚀(变成0)
erosion = cv.erode(img, kernel, iterations=1)
# 扩张
# 如果内核下的至少一个像素为“ 1”,则像素元素为“ 1”。因此,它会增加图像中的白色区域或增加前景对象的大小。通常,在消除噪音的情况下,腐蚀后会膨胀。因为腐蚀会消除白噪声,但也会缩小物体。因此,我们对其进行了扩展。由于噪音消失了,它们不会回来,但是我们的目标区域增加了。在连接对象的损坏部分时也很有用。
dilation = cv.dilate(img, kernel, iterations=1)
# 开运算
# 开放只是**侵蚀然后扩张**的另一个名称。如上文所述,它对于消除噪音很有用。
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
# 闭运算
# 闭运算与开运算相反,先扩张然后再侵蚀。在关闭前景对象内部的小孔或对象上的小黑点时很有用。
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
# 形态学梯度
# 这是图像扩张和侵蚀之间的区别。结果将看起来像对象的轮廓。
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
# 顶帽
# 它是输入图像和图像开运算之差。下面的示例针对9x9内核完成。
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
# 黑帽
# 这是输入图像和图像闭运算之差。
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)

plt.subplot(241), plt.imshow(erosion), plt.title('erosion侵蚀')
plt.xticks([]), plt.yticks([])
plt.subplot(242), plt.imshow(dilation), plt.title('dilation扩张')
plt.xticks([]), plt.yticks([])
plt.subplot(243), plt.imshow(opening), plt.title('opening开运算')
plt.xticks([]), plt.yticks([])
plt.subplot(244), plt.imshow(closing), plt.title('closing闭运算')
plt.xticks([]), plt.yticks([])
plt.subplot(245), plt.imshow(gradient), plt.title('gradient形态学梯度')
plt.xticks([]), plt.yticks([])
plt.subplot(246), plt.imshow(tophat), plt.title('tophat顶帽')
plt.xticks([]), plt.yticks([])
plt.subplot(247), plt.imshow(tophat), plt.title('blackhat黑帽')
plt.xticks([]), plt.yticks([])
plt.subplot(248), plt.imshow(img), plt.title('img原图')
plt.xticks([]), plt.yticks([])
plt.show()

# 结构元素
# 在某些情况下,您可能需要椭圆形/圆形的内核。
# 矩形内核
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
print(kernel)
# 椭圆内核
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
print(kernel)
# 十字内核
kernel = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
print(kernel)

# —————— 图像梯度 ——————
# Sobel算子
# Sobel算子是高斯平滑加微分运算的联合运算,因此它更抗噪声。逆可以指定要采用的导数方向,垂直或水平(分别通过参数yorder和xorder)。逆还可以通过参数ksize指定内核的大小。如果ksize = -1,则使用3x3 Scharr滤波器,比3x3 Sobel滤波器具有更好的结果。
# Laplacian算子
# 它计算了由某个关系给出的图像的拉普拉斯图,它是每一阶导数通过Sobel算子计算。如果ksize = 1,然后使用以下内核用于过滤:kernel=[[0 1 0],[1 -4 1],[0 1 0]]
img = cv.imread('images/shudu.png', 0)
laplacian = cv.Laplacian(img, cv.CV_64F)
sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=5)
plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray'), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray'), plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap='gray'), plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobely, cmap='gray'), plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()

# 如果要检测两个边缘,更好的选择是将输出数据类型保留为更高的形式,例如cv.CV_16S,cv.CV_64F等,取其绝对值,然后转换回cv.CV_8U。
# 下面的代码演示了用于水平Sobel滤波器,使用cv.CV_8U和CV_64F结果差异。
# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img, cv.CV_8U, 1, 0, ksize=5)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(sobelx8u, cmap='gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap='gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— Canny边缘检测 ——————
img = cv.imread('images/shudu.png', 0)
# Canny 边缘检测参数
threshold1 = 100  # 第一个阈值
threshold2 = 200  # 第二个阈值
apertureSize = 3  # 孔径大小,通常为 3、5 或 7
L2gradient = False  # 是否使用 L2 范数
# 执行 Canny 边缘检测
edges = cv.Canny(img, threshold1, threshold2, apertureSize, L2gradient=L2gradient)
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(edges, cmap='gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 图像金字塔 ——————
#   加载苹果和橙子的两个图像
#   查找苹果和橙子的高斯金字塔(在此示例中,级别数为6)
#   在高斯金字塔中,找到其拉普拉斯金字塔
#   然后在每个拉普拉斯金字塔级别中加入苹果的左半部分和橙子的右半部分
#   最后从此联合图像金字塔中重建原始图像。
A = cv.imread('images/Apple.png')
B = cv.imread('images/Orange.png')
# 生成A的高斯金字塔
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpA.append(G)
# 生成B的高斯金字塔
G = B.copy()
gpB = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpB.append(G)
# 生成A的拉普拉斯金字塔
lpA = [gpA[5]]
for i in range(5, 0, -1):
    GE = cv.pyrUp(gpA[i])
    L = cv.subtract(gpA[i - 1], GE)
    lpA.append(L)
# 生成B的拉普拉斯金字塔
lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv.pyrUp(gpB[i])
    L = cv.subtract(gpB[i - 1], GE)
    lpB.append(L)
# 现在在每个级别中添加左右两半图像
LS = []
for la, lb in zip(lpA, lpB):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols / 2], lb[:, cols / 2:]))
    LS.append(ls)
# 现在重建
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv.pyrUp(ls_)
    ls_ = cv.add(ls_, LS[i])
# 图像与直接连接的每一半
real = np.hstack((A[:, :cols / 2], B[:, cols / 2:]))
cv.imwrite('output/Pyramid_blending2.jpg', ls_)
cv.imwrite('output/Direct_blending.jpg', real)

# —————— 轮廓 ——————
# 轮廓可以简单地解释为连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于形状分析以及对象检测和识别的有用工具。
# 找到轮廓就像从黑色背景中找到白色物体。因此请记住,要找到的对象应该是白色,背景应该是黑色。
img = cv.imread('images/Apple.png')
imggray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imggray, 127, 255, 0)
# findcontour(第一个是源图像,第二个是轮廓检索模式,第三个是轮廓逼近方法)。输出等高线和层次结构。轮廓是图像中所有轮廓的Python列表。每个单独的轮廓是一个(x,y)坐标的Numpy数组的边界点的对象。
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# cv.findContours 函数的主要参数:
#   image:输入图像,通常为单通道灰度图像。这是要查找轮廓的源图像。
#   mode:表示轮廓检测模式的参数。可以是以下值之一:
#       cv.RETR_EXTERNAL:仅检测最外层轮廓。
#       cv.RETR_LIST:检测所有轮廓并将它们存储在列表中,没有任何父子关系。
#       cv.RETR_CCOMP:检测所有轮廓并将它们存储在两级层次结构中,即轮廓的外部轮廓和内部空洞轮廓。
#       cv.RETR_TREE:检测所有轮廓并将它们存储在树状结构中,包括父子关系。
#   method:表示轮廓近似方法的参数。可以是以下值之一:
#       cv.CHAIN_APPROX_NONE:保存所有轮廓点,不进行近似。
#       cv.CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,仅保留其端点。
#       cv.CHAIN_APPROX_TC89_L1 和 cv.CHAIN_APPROX_TC89_KCOS:应用 Teh-Chin 链逼近算法进行近似。
#   contours:输出参数,包含检测到的轮廓信息。通常是一个列表,每个元素代表一个检测到的轮廓。
#   hierarchy:输出参数,包含轮廓的层次信息。这是一个可选参数,通常不需要。
# 绘制轮廓,cv.drawContours(源图像,作为Python列表传递的轮廓,轮廓的索引(在绘制单个轮廓时有用。要绘制所有轮廓,请传递-1)),其余参数是颜色,厚度等等
print(contours)
drawContours1 = cv.drawContours(img, contours, -1, (0, 255, 0), 3)  # 在图像中绘制所有轮廓
drawContours2 = cv.drawContours(img, contours, 3, (0, 255, 0), 3)  # 绘制单个轮廓,如第四个轮廓
# 但是在大多数情况下,以下方法会很有用
cnt = contours[4]
drawContours3 = cv.drawContours(img, [cnt], 0, (0, 255, 0), 3)
plt.figure("轮廓")
plt.subplot(221), plt.imshow(img, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(drawContours1, cmap='gray')
plt.title('drawContours1 Image'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(drawContours2, cmap='gray')
plt.title('drawContours2 Image'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.imshow(drawContours3, cmap='gray')
plt.title('drawContours3 Image'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 轮廓特征 ——————
# 1 特征矩
# 特征矩可以帮助您计算一些特征,例如物体的质心,物体的面积等。请查看特征矩上的维基百科页面。函数**cv.moments**()提供了所有计算出的矩值的字典。
img = cv.imread('images/approxPolyDP.png', 0)
ret, thresh = cv.threshold(img, 127, 255, 0)  # 转换为二值图,ret是最终选择的阈值,thresh是二值化后输出的图像
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)  # 轮廓提取
cnt = contours[0]
M = cv.moments(cnt)
print(M)
# 质心的求法:cx=M10/M00,cy=M01/M00
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
print(f"质心:({cx},{cy})")
# 2 轮廓面积
area = cv.contourArea(cnt)
print(f"area={area}")
# 3 轮廓周长
perimeter = cv.arcLength(cnt, True)  # 第二个参数指定形状是闭合轮廓(True)还是曲线。
print(f"perimeter={perimeter}")
# 4 轮廓近似
# 根据我们指定的精度,它可以将轮廓形状近似为顶点数量较少的其他形状
temp = np.zeros(img.shape, np.uint8)  # 生成黑背景
img1 = cv.drawContours(temp, contours, -1, (255, 255, 255), 2)  # 原始轮廓
temp = np.zeros(img.shape, np.uint8)  # 生成黑背景
epsilon = 0.1 * cv.arcLength(contours[0], True)
approx = cv.approxPolyDP(contours[0], epsilon, True)
img2 = cv.polylines(temp, [approx], True, (255, 255, 255), 2)  # 轮廓近似后的结果
names = ['原图', '轮廓检测结果', '轮廓近似后结果']
images = [img, img1, img2]
plt.figure("轮廓近似")
for i in range(1):
    for j in range(3):
        plt.subplot(1, 3, i * 3 + j + 1), plt.imshow(images[i * 3 + j], cmap='gray')
        plt.title(names[i * 3 + j], fontsize=30), plt.xticks([]), plt.yticks([])
        num = i * 3 + j
        if num >= len(names) - 1:
            break
plt.show()
# 5 检查凸度
# cv.convexHull()函数检查曲线是否存在凸凹缺陷并对其进行校正
img = cv.imread('images/hand.png', 0)  # 读取二进制手图像
img_contour = img.copy()
# 得到灰度图做Canny边缘检测
edges = cv.Canny(img, 120, 255, 0)
# 提取并绘制轮廓
contours, hierarchy = cv.findContours(edges, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
img_contour = cv.drawContours(img_contour, contours, -1, (0, 255, 0), 2)
# 凸包检测
# hull = cv.convexHull(points, clockwise, returnpoints)
#   hull: 输出凸包结果,n*1*2数据结构,n为外包围圈的点的个数
#   points: 输入的坐标点,通常为1* n * 2 结构,n为所有的坐标点的数目
#   clockwise:转动方向,TRUE为顺时针,否则为逆时针;
#   returnPoints:默认为TRUE,返回凸包上点的坐标,如果设置为FALSE,会返回与凸包点对应的轮廓上的点。
hulls = []
for contour in contours:
    k = cv.isContourConvex(contour)  # cv.isContourConvex()检查曲线是否凸出的功能。返回True还是False
    print(f"contour的凸出功能:{k}")
    hull = cv.convexHull(contour)
    hulls.append(hull)
img_convex_hull = cv.drawContours(img, hulls, -1, (0, 255, 0), 2)
plt.figure("凸包检测")
plt.subplot(221), plt.imshow(img, cmap='gray'), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(edges, cmap='gray'), plt.title('edges'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(img_contour, cmap='gray'), plt.title('img_contour'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.imshow(img_convex_hull, cmap='gray'), plt.title('img_convex_hull'), plt.xticks([]), plt.yticks([])
plt.show()

# 6 边界矩形
img = cv.imread('images/hand.png', 0)  # 读取二进制手图像
ret, thresh = cv.threshold(img, 127, 255, 0)  # 转换为二值图,ret是最终选择的阈值,thresh是二值化后输出的图像
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
# a.直角矩形:它是一个矩形,不考虑物体的旋转。所以边界矩形的面积不是最小的。它是由函数**cv.boundingRect**()找到的。
img_contour_rectangle = img.copy()
for contour in contours:
    x, y, w, h = cv.boundingRect(contour)  # 令(x,y)为矩形的左上角坐标,而(w,h)为矩形的宽度和高度。
    cv.rectangle(img_contour_rectangle, (x, y), (x + w, y + h), (0, 255, 0), 2)
# b. 旋转矩形:边界矩形是用最小面积绘制的,所以它也考虑了旋转。中包含以下细节 -(中心(x,y),(宽度,高度),旋转角度)。但要画出这个矩形,我们需要矩形的四个角。
img_contour_rotate = img.copy()
for contour in contours:
    rect = cv.minAreaRect(contour)
    box = cv.boxPoints(rect)
    box = np.intp(box)
    cv.drawContours(img_contour_rotate, [box], 0, (0, 0, 255), 2)

plt.figure("边界矩形")
plt.subplot(221), plt.imshow(img), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(img_contour_rectangle), plt.title('img_contour_rectangle'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(img_contour_rotate), plt.title('img_contour_rotate'), plt.xticks([]), plt.yticks([])
plt.show()

# 7最小闭合圈
img_contour_circle = img.copy()
for contour in contours:
    (x, y), radius = cv.minEnclosingCircle(contour)
    center = (int(x), int(y))
    radius = int(radius)
    cv.circle(img_contour_circle, center, radius, (0, 255, 0), 2)
plt.figure("7最小闭合圈")
plt.subplot(221), plt.imshow(img), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(img_contour_circle), plt.title('img_contour_circle'), plt.xticks([]), plt.yticks([])
plt.show()

# 8拟合一个椭圆
img_contour_ellipse = img.copy()
contours, hierarchy = cv.findContours(img_contour_ellipse, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
for contour in contours:
    if len(contour) >= 5:
        ellipse = cv.fitEllipse(contour)
        cv.ellipse(img_contour_ellipse, ellipse, (0, 255, 0), 2)
plt.figure("8拟合一个椭圆")
plt.subplot(221), plt.imshow(img), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(img_contour_ellipse), plt.title('img_contour_ellipse'), plt.xticks([]), plt.yticks([])
plt.show()

# 9拟合直线
img_contour_line = img.copy()
rows, cols = img.shape[:2]
for contour in contours:
    [vx, vy, x, y] = cv.fitLine(contour, cv.DIST_L2, 0, 0.01, 0.01)
    lefty = int((-x * vy / vx) + y)
    righty = int(((cols - x) * vy / vx) + y)
    cv.line(img_contour_line, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)
plt.figure("9拟合直线")
plt.subplot(221), plt.imshow(img), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(img_contour_line), plt.title('img_contour_line'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 轮廓属性 ——————
img = cv.imread('images/approxPolyDP.png', 0)
ret, thresh = cv.threshold(img, 127, 255, 0)  # 转换为二值图,ret是最终选择的阈值,thresh是二值化后输出的图像
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)  # 轮廓提取
cnt = contours[0]
# 1. 长宽比
x, y, w, h = cv.boundingRect(cnt)
aspect_ratio = float(w) / h  # 它是对象边界矩形的宽度与高度的比值。
# 2. 范围
area = cv.contourArea(cnt)
x, y, w, h = cv.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area  # 范围是轮廓区域与边界矩形区域的比值。
# 3. 坚实度
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area) / hull_area  # 坚实度是等高线面积与其凸包面积之比。
# 4.等效直径
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4 * area / np.pi)  # 等效直径是面积与轮廓面积相同的圆的直径。
# 5. 取向
(x, y), (MA, ma), angle = cv.fitEllipse(cnt)  # 取向是物体指向的角度。以下方法还给出了主轴和副轴的长度。
# 6. 掩码和像素点
# 在某些情况下,我们可能需要构成该对象的所有点。
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
mask = np.zeros(imgray.shape, np.uint8)
cv.drawContours(mask, [cnt], 0, 255, -1)
# Numpy给出的坐标是(行、列)格式,而OpenCV给出的坐标是(x,y)格式。所以基本上答案是可以互换的。注意,row = x, column = y。
pixelpoints_np = np.transpose(np.nonzero(mask))
pixelpoints_cv = cv.findNonZero(mask)
# 7. 最大值,最小值和它们的位置
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray, mask=mask)
# 8平均颜色或平均强度
mean_val = cv.mean(img, mask=mask)  # 在这里,我们可以找到对象的平均颜色。或者可以是灰度模式下物体的平均强度。
# 9. 极端点
# 极点是指对象的最顶部,最底部,最右侧和最左侧的点。
# leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
# rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
# topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
# bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
image = cv.imread('images/approxPolyDP.png', 0)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
_, thresholded = cv.threshold(gray, 200, 255, cv.THRESH_BINARY)
contours, _ = cv.findContours(thresholded, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 找到凸包
for contour in contours:
    hull = cv.convexHull(contour, returnPoints=False)
    defects = cv.convexityDefects(contour, hull)

    if defects is not None:
        for i in range(defects.shape[0]):
            s, e, f, d = defects[i, 0]
            start = tuple(contour[s][0])
            end = tuple(contour[e][0])
            far = tuple(contour[f][0])
            cv.circle(image, far, 5, [0, 0, 255], -1)

# 显示图像
cv.imshow('Image with Convex Hull and Far Points', image)
cv.waitKey(0)
cv.destroyAllWindows()

# —————— 轮廓:更多属性 ——————
# 1. 凸性缺陷
# 从这个凸包上的任何偏差找到凸性缺陷
img = cv.imread('images/star.png')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(img_gray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 2, 1)
cnt = contours[0]
hull = cv.convexHull(cnt, returnPoints=False)
defects = cv.convexityDefects(cnt, hull)
for i in range(defects.shape[0]):
    s, e, f, d = defects[i, 0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv.line(img, start, end, [0, 255, 0], 2)
    cv.circle(img, far, 5, [0, 0, 255], -1)
cv.imshow('凸性缺陷', img)
cv.waitKey(0)
cv.destroyAllWindows()
# 2. 点多边形测试
# 这个函数找出图像中一点到轮廓线的最短距离。它返回的距离,点在轮廓线外时为负,点在轮廓线内时为正,点在轮廓线上时为零。
dist = cv.pointPolygonTest(cnt, (50, 50),
                           True)  # 第三个参数是measureDist。如果它是真的,它会找到有符号的距离。如果为假,则查找该点是在轮廓线内部还是外部(分别返回+1、-1和0)
print(dist)
# 3. 形状匹配
# cv.matchShapes()能够比较两个形状或两个轮廓,并返回一个显示相似性的度量。结果越低,匹配越好。它是根据矩值计算出来的。
img1 = cv.imread('images/star.png', 0)
img2 = cv.imread('images/star2.png', 0)
ret, thresh1 = cv.threshold(img1, 127, 255, 0)
ret, thresh2 = cv.threshold(img2, 127, 255, 0)
contours1, hierarchy = cv.findContours(thresh1, 2, 1)
cnt1 = contours1[0]
contours2, hierarchy = cv.findContours(thresh2, 2, 1)
cnt2 = contours2[0]
ret = cv.matchShapes(cnt1, cnt2, 1, 0.0)
print(ret)

# —————— 轮廓分层 ——————
#  RETR_LIST
# 这是四个标志中最简单的一个(从解释的角度来看)。它只是检索所有的轮廓,但不创建任何亲子关系。在这个规则下,父轮廓和子轮廓是平等的,他们只是轮廓。他们都属于同一层级。
# 2. RETR_EXTERNAL
# 如果使用此标志,它只返回极端外部标志。所有孩子的轮廓都被留下了。我们可以说,根据这项规则,每个家庭只有长子得到关注。它不关心家庭的其他成员:)。
# 3. RETR_CCOMP
# 此标志检索所有轮廓并将其排列为2级层次结构。物体的外部轮廓(即物体的边界)放在层次结构-1中。对象内部孔洞的轮廓(如果有)放在层次结构-2中。如果其中有任何对象,则其轮廓仅在层次结构1中重新放置。以及它在层级2中的漏洞等等。
# 4. RETR_TREE
# 这是最后一个家伙,完美先生。它检索所有的轮廓并创建一个完整的家族层次结构列表。它甚至告诉,谁是爷爷,父亲,儿子,孙子,甚至更多…:)。

# —————— 直方图 ——————
# 通过查看图像的直方图,您可以直观地了解该图像的对比度,亮度,强度分布等。
# cv.calcHist(images,channels,mask,histSize,ranges [,hist [,accumulate]])
#   images:它是uint8或float32类型的源图像。它应该放在方括号中,即“ [img]”。
#   channels:也以方括号给出。它是我们计算直方图的通道的索引。例如,如果输入为灰度图像,则其值为[0]。对于彩色图像,您可以传递[0],[1]或[2]分别计算蓝色,绿色或红色通道的直方图。
#   mask:图像掩码。为了找到完整图像的直方图,将其指定为“无”。但是,如果要查找图像特定区域的直方图,则必须为此创建一个掩码图像并将其作为掩码。(我将在后面显示一个示例。)
#   histSize:这表示我们的BIN计数。需要放在方括号中。对于全尺寸,我们通过[256]。
#   ranges:这是我们的RANGE。通常为[0,256]。
img = cv.imread('images/star.png', 0)
# 1.1 OpenCV中的直方图计算
hist_cv = cv.calcHist([img], [0], None, [256], [0, 256])
# 1.2 numpy的直方图计算
hist_np, bins = np.histogram(img.ravel(), 256, [0, 256])
plt.figure("OpenCV & Numpy中的直方图计算")
plt.subplot(131), plt.imshow(img), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.plot(hist_cv), plt.title('OpenCV中的直方图计算hist')
plt.subplot(133), plt.plot(hist_np), plt.title('Numpy中的直方图计算hist')
plt.show()
# 2.1 使用Matplotlib绘制直方图
plt.figure("使用Matplotlib绘制直方图")
plt.hist(img.ravel(), 256, [0, 256]);
plt.show()
# 使用matplotlib的法线图,这对于BGR图是很好的
imgRGB = cv.imread('images/1.png')
color = ('b', 'g', 'r')
plt.figure("BGR图的直方图计算")
for i, col in enumerate(color):
    histr = cv.calcHist([imgRGB], [i], None, [256], [0, 256])
    plt.plot(histr, color=col)
    plt.xlim([0, 256])
plt.show()
# 2.2 使用OpenCV绘制直方图 用cv.line
# 2.3 掩码的应用
# 如果你想找到图像某些区域的直方图可以传入掩码
img = cv.imread('images/shudu.png', 0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img, img, mask=mask)
# 计算掩码区域和非掩码区域的直方图
# 检查作为掩码的第三个参数
hist_full = cv.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv.calcHist([img], [0], mask, [256], [0, 256])
plt.figure("掩码图像直方图")
plt.subplot(221), plt.imshow(img, 'gray'), plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('掩码'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('掩码后图像'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.plot(hist_full, label='原图像直方图'), plt.plot(hist_mask, label='掩码图像直方图'), plt.xlabel(
    'Pixel Value'), plt.ylabel('Frequency')
plt.xlim([0, 256])  # 为了设置 x 轴的显示范围
plt.show()
# 3 直方图均衡
# 将这个直方图拉伸到两端(如下图所示,来自wikipedia),这就是直方图均衡化的作用(简单来说)。这通常会提高图像的对比度。
img = cv.imread('images/star.png', 0)
hist_cv = cv.calcHist([img], [0], None, [256], [0, 256])  # 直方图
plt.figure("直方图均衡")
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('原图')
plt.subplot(132), plt.plot(hist_cv), plt.title('直方图')
# 直方图均衡
hist, bins = np.histogram(img.flatten(), 256, [0, 256])
cdf = hist.cumsum()
cdf_normalized = cdf * float(hist.max()) / cdf.max()
plt.subplot(133), plt.plot(cdf_normalized, color='b'), plt.hist(img.flatten(), 256, [0, 256], color='r'), plt.title(
    '直方图均衡')
plt.xlim([0, 256])
plt.legend(('cdf', 'histogram'), loc='upper left')
plt.show()
# 定义查找表
cdf_m = np.ma.masked_equal(cdf, 0)
cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())
cdf = np.ma.filled(cdf_m, 0).astype('uint8')
img2 = cdf[img]  # 应用变换
plt.figure("直方图均衡")
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('原图')
plt.subplot(122), plt.imshow(img2, 'gray'), plt.title('直方图均衡后图像')
plt.show()
# OpenCV中的直方图均衡
# 它的输入只是灰度图像,输出是我们的直方图均衡图像。
img = cv.imread('images/star.png', 0)
equ = cv.equalizeHist(img)
res = np.hstack((img, equ))  # stacking images side-by-side
plt.figure("OpenCV中的直方图均衡")
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('原图')
plt.subplot(122), plt.imshow(res, 'gray'), plt.title('OpenCV中的直方图均衡')
plt.show()

# 自适应直方图均衡:在这种情况下,图像被分成称为“tiles”的小块(在OpenCV中,tileSize默认为8x8)。然后,像往常一样对这些块中的每一个进行直方图均衡。
img = cv.imread('images/star.png', 0)
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl1 = clahe.apply(img)
plt.figure("自适应直方图均衡")
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('原图')
plt.subplot(122), plt.imshow(cl1, 'gray'), plt.title('自适应直方图均衡')
plt.show()

# 4 二维直方图
# 对于颜色直方图,我们需要将图像从BGR转换为HSV。(请记住,对于一维直方图,我们从BGR转换为灰度)
# cv.calcHist([image],channel,mask,bins,range)
img = cv.imread('images/1.png')
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
# 4.1 OpenCV中的二维直方图
hist_2d_cv = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
# channel = [0,1],因为我们需要同时处理H和S平面。
# bins = [180,256] 对于H平面为180,对于S平面为256。
# range = [0,180,0,256] 色相值介于0和180之间,饱和度介于0和256之间。
# 4.2 Numpy中的二维直方图
h, s, v = cv.split(hsv)
hist_2d_np, xbins, ybins = np.histogram2d(h.ravel(), s.ravel(), [180, 256], [[0, 180], [0, 256]])

plt.figure("二维直方图")
plt.subplot(121), plt.imshow(hist_2d_cv, interpolation='nearest'), plt.title('opencv二维直方图')
plt.subplot(122), plt.imshow(hist_2d_np, interpolation='nearest'), plt.title('numpy二维直方图')
plt.show()

# 5 直方图反投影
# cv.calcBackProject()的一个参数是直方图,也就是物体的直方图。在传递给backproject函数之前,应该对对象直方图进行归一化。它返回概率图像。然后我们用圆盘内核对图像进行卷积并应用阈值。
roi = cv.imread('images/1.png')
hsv = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
target = cv.imread('images/1.png')
hsvt = cv.cvtColor(target, cv.COLOR_BGR2HSV)
# 计算对象的直方图
roihist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
# 直方图归一化并利用反传算法
cv.normalize(roihist, roihist, 0, 255, cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt], [0, 1], roihist, [0, 180, 0, 256], 1)
# 用圆盘进行卷积
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
cv.filter2D(dst, -1, disc, dst)
# 应用阈值作与操作
ret, thresh = cv.threshold(dst, 50, 255, 0)
thresh = cv.merge((thresh, thresh, thresh))
res = cv.bitwise_and(target, thresh)
res = np.vstack((target, thresh, res))
plt.figure("直方图反投影")
plt.subplot(121), plt.imshow(roi, interpolation='nearest'), plt.title('原图')
plt.subplot(122), plt.imshow(res, interpolation='nearest'), plt.title('直方图反投影')
plt.show()

# —————— 傅里叶变换 ——————
# numpy中的傅里叶变换
img = cv.imread('images/1.png', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))
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()
rows, cols = img.shape
crow, ccol = rows // 2, cols // 2
fshift[crow - 30:crow + 31, ccol - 30:ccol + 31] = 0
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.real(img_back)
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img_back, cmap='gray'), plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img_back), plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
plt.show()
# OpenCV中的傅里叶变换
# cv.dft()和cv.idft()函数。它返回两个通道:第一个通道是结果的实部,第二个通道是结果的虚部。输入图像首先应转换为np.float32。
img = cv.imread('images/zlh.jpg', 0)
dft = cv.dft(np.float32(img), flags=cv.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20 * np.log(cv.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()
rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)
# 首先创建一个掩码,中心正方形为1,其余全为零
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1
# 应用掩码和逆DFT
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv.idft(f_ishift)
img_back = cv.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('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

# 为什么拉普拉斯算子是高通滤波器?
# 没有缩放参数的简单均值滤波器
mean_filter = np.ones((3, 3))
# 创建高斯滤波器
x = cv.getGaussianKernel(5, 10)
gaussian = x * x.T
# 不同的边缘检测滤波器
# x方向上的scharr
scharr = np.array([[-3, 0, 3],
                   [-10, 0, 10],
                   [-3, 0, 3]])
# x方向上的sobel
sobel_x = np.array([[-1, 0, 1],
                    [-2, 0, 2],
                    [-1, 0, 1]])
# y方向上的sobel
sobel_y = np.array([[-1, -2, -1],
                    [0, 0, 0],
                    [1, 2, 1]])
# 拉普拉斯变换
laplacian = np.array([[0, 1, 0],
                      [1, -4, 1],
                      [0, 1, 0]])
filters = [mean_filter, gaussian, laplacian, sobel_x, sobel_y, scharr]
filter_name = ['mean_filter', 'gaussian', 'laplacian', 'sobel_x', 'sobel_y', 'scharr_x']
fft_filters = [np.fft.fft2(x) for x in filters]
fft_shift = [np.fft.fftshift(y) for y in fft_filters]
mag_spectrum = [np.log(np.abs(z) + 1) for z in fft_shift]
for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(mag_spectrum[i], cmap='gray')
    plt.title(filter_name[i]), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 模板匹配 ——————
# 1 OpenCV中的模板匹配
img = cv.imread('images/zlh.jpg', 0)
img2 = img.copy()
template = cv.imread('images/zlh_template.png', 0)
w, h = template.shape[::-1]
# 列表中所有的6种比较方法
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
           'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
fig, axs = plt.subplots(4, 2, figsize=(10, 10))
for i, meth in enumerate(methods):
    img = img2.copy()
    method = eval(meth)
    # 应用模板匹配
    res = cv.matchTemplate(img, template, method)
    min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
    # 如果方法是TM_SQDIFF或TM_SQDIFF_NORMED,则取最小值
    if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)
    cv.rectangle(img, top_left, bottom_right, 255, 2)

    axs[i // 2, i % 2].imshow(img, cmap='gray')
    axs[i // 2, i % 2].set_title(meth)
    axs[i // 2, i % 2].axis('off')
    # plt.subplot(121),plt.imshow(res,cmap = 'gray')
    # plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
    # plt.subplot(122),plt.imshow(img,cmap = 'gray')
    # plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
    # plt.suptitle(meth)
    # plt.show()
axs[3, 0].imshow(img, cmap='gray')
axs[3, 0].set_title("原图")
axs[3, 0].axis('off')
axs[3, 1].imshow(template, cmap='gray')
axs[3, 1].set_title("模板")
axs[3, 1].axis('off')
plt.show()

# 2 多对象的模板匹配
img = cv.imread('images/zlh.jpg', cv.COLOR_BGR2RGB)
img_rgb = img.copy()
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('images/zlh_template.png', 0)
w, h = template.shape[::-1]
res = cv.matchTemplate(img_gray, template, cv.TM_CCOEFF)
threshold = 0.8
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
    cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)
cv.imwrite('res.png', img_rgb)

plt.subplot(231), plt.imshow(img, cmap='gray')
plt.title('img'), plt.xticks([]), plt.yticks([])
plt.subplot(232), plt.imshow(img_gray, cmap='gray')
plt.title('img_gray'), plt.xticks([]), plt.yticks([])
plt.subplot(233), plt.imshow(template, cmap='gray')
plt.title('template'), plt.xticks([]), plt.yticks([])
plt.subplot(234), plt.imshow(res, cmap='gray')
plt.title('res'), plt.xticks([]), plt.yticks([])
plt.subplot(235), plt.imshow(img_rgb, cmap='gray')
plt.title('img_rgb'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 霍夫线变换 ——————
# 1 OpenCV中的霍夫曼变换
image = cv.imread('images/shudu.png')
img = image.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 30, 90, apertureSize=3)
# cv.HoughLines( image, rho,theta , threshold, srn,min_theta )
#   image:输入的二值图像,通常是边缘检测后的图像(例如 Canny 边缘检测后的结果)。
#   rho:极坐标中的距离分辨率,表示以像素为单位的距离精度。通常设置为 1.0。
#   theta:极坐标中的角度分辨率,表示弧度为单位的角度精度。通常设置为 numpy.pi/180,以弧度为单位。
#   threshold:阈值参数,用于确定检测到的直线。只有投票数超过阈值的直线才会被返回。阈值越高,返回的直线越少,阈值越低,返回的直线越多。
#   lines:输出参数,返回检测到的直线的极坐标参数。通常是一个numpy数组,每一行包含一组 rho 和 theta。
#   srn 和 stn:可选参数,rho 和 theta 的分辨率扩展因子。默认情况下为1.0。
#   min_theta 和 max_theta:可选参数,表示允许检测到的直线的角度范围。通常设置为0和numpy.pi。
lines = cv.HoughLines(edges, 1, np.pi / 180, 100)  # 若阈值设置过高,会导致lines返回None
for line in lines:
    rho, theta = line[0]  # rho以像素为单位,theta以弧度为单位。
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * (a))
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * (a))
    cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
plt.figure("霍夫线变换")
plt.subplot(221), plt.imshow(image, cmap='gray'), plt.title('原图'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(edges, cmap='gray'), plt.title('Canny检测边缘'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(img, cmap='gray'), plt.title('Opencv霍夫线变换'), plt.xticks([]), plt.yticks([])

# 2 概率霍夫变换
# 概率霍夫变换是我们看到的霍夫变换的优化。它没有考虑所有要点。取而代之的是,它仅采用随机的点子集,足以进行线检测。只是我们必须降低阈值。它直接返回行的两个端点。
img = image.copy()
lines = cv.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=100, maxLineGap=10)
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
plt.subplot(224), plt.imshow(img, cmap='gray'), plt.title('概率霍夫变换'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 霍夫圈变换 ——————
img = cv.imread('images/opencv-logo.png', 0)
img = cv.medianBlur(img, 5)  # 中值滤波的原理是对图像中的每个像素点,以其邻域内像素的中值来替代该像素的值。这有助于去除图像中的离群值和噪声点,而不会过多模糊图像的细节。
cimg = cv.cvtColor(img, cv.COLOR_GRAY2BGR)
# HoughCircles(image, method,dp ,minDist,param1,param2,minRadius):
#   image:输入的二值图像,通常是边缘检测后的图像或其他经过预处理的图像。
#   method:定义检测方法,可以选择不同的方法,通常使用 cv.HOUGH_GRADIENT。这表示使用基于梯度的霍夫变换。
#   dp:累加器分辨率与图像分辨率的比例。通常设置为 1。
#   minDist:检测到的圆之间的最小距离。这个参数用于控制检测到的圆之间的最小间隔距离。
#   param1:用于 Canny 边缘检测的高阈值。通常设置为较低的值。
#   param2:累加器阈值,用于确定检测到的圆。只有投票数超过阈值的圆才会被返回。阈值越高,返回的圆越少,阈值越低,返回的圆越多。
#   minRadius 和 maxRadius:允许检测到的圆的半径范围。
#   circles:输出参数,返回检测到的圆的参数。通常是一个numpy数组,每一行包含一组 (x, y, radius)。
circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 1, 20,
                          param1=50, param2=30, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
    # 绘制外圆
    cv.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
    # 绘制圆心
    cv.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
cv.imshow('detected circles', cimg)
cv.waitKey(0)
cv.destroyAllWindows()

# —————— 图像分割与Watershed算法 ——————
image = cv.imread('images/coins.png', cv.IMREAD_COLOR)
img = image.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# 噪声去除 开运算
kernel = np.ones((3, 3), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)  # 执行膨胀、腐蚀、开运算、闭运算等操作。
# 确定背景区域
sure_bg = cv.dilate(opening, kernel, iterations=3)
# 寻找前景区域
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 找到未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg)
# 类别标记
ret, markers = cv.connectedComponents(sure_fg)
# 为所有的标记加1,保证背景是0而不是1
markers = markers + 1
# 现在让所有的未知区域为0
markers[unknown == 255] = 0
# 使用分水岭算法。然后标记图像将被修改。边界区域将标记为-1
markers = cv.watershed(img, markers)
img[markers == -1] = [255, 0, 0]

plt.figure("图像分割与Watershed算法")
plt.subplot(331), plt.imshow(cv.cvtColor(image, cv.COLOR_BGR2RGB)), plt.title('image原图'), plt.xticks([]), plt.yticks(
    [])
plt.subplot(332), plt.imshow(thresh), plt.title('thresh使用Otsu的二值化'), plt.xticks([]), plt.yticks([])
plt.subplot(333), plt.imshow(opening), plt.title('opening开运算'), plt.xticks([]), plt.yticks([])
plt.subplot(334), plt.imshow(sure_bg), plt.title('sure_bg背景区域'), plt.xticks([]), plt.yticks([])
plt.subplot(335), plt.imshow(dist_transform), plt.title('dist_transform'), plt.xticks([]), plt.yticks([])
plt.subplot(336), plt.imshow(sure_fg), plt.title('sure_fg前景区域'), plt.xticks([]), plt.yticks([])
plt.subplot(337), plt.imshow(unknown), plt.title('unknown未知区域'), plt.xticks([]), plt.yticks([])
plt.subplot(338), plt.imshow(markers), plt.title('markers类别标记'), plt.xticks([]), plt.yticks([])
plt.subplot(339), plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)), plt.title('img结果'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— 交互式前景提取使用GrabCut算法 ——————
img = cv.imread('images/coins.png')
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 450, 290)
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask2[:, :, np.newaxis]
plt.imshow(img), plt.colorbar(), plt.show()
# newmask是我手动标记过的mask图像
newmask = cv.imread('newmask.png', 0)
# 标记为白色(确保前景)的地方,更改mask = 1
# 标记为黑色(确保背景)的地方,更改mask = 0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv.grabCut(img, mask, None, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_MASK)
mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img = img * mask[:, :, np.newaxis]
plt.imshow(img), plt.colorbar(), plt.show()

6.特征检测与描述

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 设置matplotlib.pyplot显示中文字体
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置字体
plt.rcParams["axes.unicode_minus"] = False  # 该语句解决图像中的“-”负号的乱码问题

# —————— 哈里斯角检测 ——————
# 哈里斯角检测的主要目标是检测图像中具有明显角度变化的位置。
# 哈里斯角检测的基本思想是,角点处的灰度值在多个方向上变化明显,即沿着各个方向都有大的梯度值。这可以通过计算像素领域内灰度值的变化来判断。核心计算公式如下:
# 1 OpenCV中的哈里斯角检测
img = cv.imread("images/watermark.bmp")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
# cornerHarris(img输入图像为float32,blocksize拐角检测考虑的邻域大小,ksizesobel导数的光圈参数,k哈里斯检测器自由参数)
dst = cv.cornerHarris(gray, 2, 3, 0.04)
dst = cv.dilate(dst, None)  # 膨胀
# 最佳值的阈值,它可能因图像而异。
img[dst > 0.01 * dst.max()] = [0, 0, 255]
cv.imshow('OpenCV中的哈里斯角检测', img)
if cv.waitKey(0) & 0xff == 27:
    cv.destroyAllWindows()
# 2 SubPixel精度的转角
# 进一步细化了以亚像素精度检测到的角落。
# 下面是一个例子。和往常一样,我们需要先找到哈里斯角。然后我们通过这些角的质心(可能在一个角上有一堆像素,我们取它们的质心)来细化它们。Harris角用红色像素标记,精制角用绿色像素标记。对于这个函数,我们必须定义何时停止迭代的条件。我们在特定的迭代次数或达到一定的精度后停止它,无论先发生什么。我们还需要定义它将搜索角落的邻居的大小。
img = cv.imread("images/watermark.bmp")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 寻找哈里斯角
gray = np.float32(gray)
dst = cv.cornerHarris(gray, 2, 3, 0.04)
dst = cv.dilate(dst, None)
ret, dst = cv.threshold(dst, 0.01 * dst.max(), 255, 0)
dst = np.uint8(dst)
# 寻找质心
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
# 定义停止和完善拐角的条件
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv.cornerSubPix(gray, np.float32(centroids), (5, 5), (-1, -1), criteria)
# 绘制
res = np.hstack((centroids, corners))
res = np.intp(res)
img[res[:, 1], res[:, 0]] = [0, 0, 255]
img[res[:, 3], res[:, 2]] = [0, 255, 0]
cv.imshow('SubPixel精度的转角', img)
cv.waitKey(0)
cv.destroyAllWindows()
# —————— Shi-tomas拐角检测器和益于跟踪的特征 ——————
img = cv.imread("images/watermark.bmp")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# cv.goodFeaturesToTrack()参数:
#   image:输入图像,通常是灰度图像(单通道)。
#   maxCorners:要检测的最大角点数目,通常是一个整数。
#   qualityLevel:角点的质量因子,通常在 0 到 1 之间。只有质量大于此值的角点才会被保留。
#   minDistance:检测到的角点之间的最小欧氏距离。如果两个角点距离太近,其中一个可能会被过滤。
#   blockSize:角点检测时的窗口大小。通常是一个奇数,表示在每个像素周围的局部窗口。
#   useHarrisDetector:一个布尔值,如果为True,则使用哈里斯角检测,如果为False,则使用 Shi-Tomasi 角检测。默认为False。
#   k:Harris 角检测的自由参数(仅在 useHarrisDetector=True 时生效)。通常在 0.04 到 0.06 之间。
# corners:输出参数,包含检测到的角点的坐标。这通常是一个N x 2的NumPy数组,其中N是检测到的角点数目。
corners = cv.goodFeaturesToTrack(gray, 25, 0.01, 10)
corners = np.intp(corners)
for i in corners:
    x, y = i.ravel()
    cv.circle(img, (x, y), 3, 255, -1)
cv.imshow('Shi-tomas拐角检测器和益于跟踪的特征', img)
cv.waitKey(0)
cv.destroyAllWindows()

# —————— SIFT尺度不变特征变换 ——————
# SIFT算法主要包括四个步骤。1. 尺度空间极值检测 2. 关键点定位 3. 方向分配 4. 关键点描述 5. 关键点匹配
# OpenCV中的SIFT
img = cv.imread('images/watermark.bmp')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()  # 创建了一个 SIFT 特征检测器对象
keypoints = sift.detect(gray, None)  # sift.detect()检测图像中的SIFT特征点
keypoints, descriptors = sift.compute(gray, keypoints)  # 提取SIFT特征点的描述子
sift_keypoints = cv.drawKeypoints(gray, keypoints, outImage=None,
                                  flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)  # 该函数在关键点的位置绘制小圆圈
plt.figure("OpenCV中的SIFT")
plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(sift_keypoints), plt.title('sift_keypoints'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— SURF简介(加速的强大功能) ——————
# SIFT用于关键点检测和描述符。但相对缓慢,人们需要更多的加速版本_SURF
# OpenCV中的SURF
img = cv.imread('images/watermark.bmp', 0)
surf = cv.xfeatures2d.SURF_create()  # 这里设置海森矩阵的阈值为400 (版本过高这句话会报错)
kp, des = surf.detectAndCompute(img, None)  # 直接查找关键点和描述符
print(len(kp))
# 图片中无法显示1199个关键点。我们将其减少到50左右以绘制在图像上。匹配时,我们可能需要所有这些功能,但现在不需要。因此,我们增加了海森阈值。
surf.setHessianThreshold(50000)  # 在实际情况下,最好将值设为300-500
kp, des = surf.detectAndCompute(img, None)
print(len(kp))
suft_keypoints = cv.drawKeypoints(img, kp, None, (255, 0, 0), 4)  # 该函数在关键点的位置绘制小圆圈
# 应用U-SURF,以便它不会找到方向
print(surf.getUpright())  # False
surf.setUpright(True)
kp = surf.detect(img, None)
suft_keypoints2 = cv.drawKeypoints(img, kp, None, (255, 0, 0), 4)

plt.figure("OpenCV中的SUFT")
plt.subplot(131), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(suft_keypoints), plt.title('suft_keypoints'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(suft_keypoints2), plt.title('suft_keypoints2-开启U-SURF'), plt.xticks([]), plt.yticks([])
plt.show()

print(surf.descriptorSize())  # 找到算符的描述
print(surf.getExtended())  # 表示flag “extened” 为False。
surf.setExtended(True)  # 将其设为True即可获取128个尺寸的描述符。
kp, des = surf.detectAndCompute(img, None)
print(surf.descriptorSize())  # 128
print(des.shape)

# —————— 用于角点检测的FAST(加速分段测试的特征)算法 ——————
# 它比其他现有的拐角检测器快几倍,但是它对高水平的噪声并不鲁棒。它取决于阈值。
# OpenCV中的高速拐角检测器
img = cv.imread('images/shudu.png', 0)
# 用默认值初始化FAST对象
fast = cv.FastFeatureDetector_create()
# 寻找并绘制关键点
kp = fast.detect(img, None)
img2 = cv.drawKeypoints(img, kp, None, color=(255, 0, 0))
# 打印所有默认参数
print("Threshold: {}".format(fast.getThreshold()))
print("nonmaxSuppression:{}".format(fast.getNonmaxSuppression()))
print("neighborhood: {}".format(fast.getType()))
print("Total Keypoints with nonmaxSuppression: {}".format(len(kp)))
cv.imwrite('fast_true.png', img2)
# 关闭非极大抑制
fast.setNonmaxSuppression(0)
kp = fast.detect(img, None)
print("Total Keypoints without nonmaxSuppression: {}".format(len(kp)))
img3 = cv.drawKeypoints(img, kp, None, color=(255, 0, 0))
cv.imwrite('fast_false.png', img3)
plt.figure("OpenCV中的用于角点检测的FAST")
plt.subplot(131), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img2), plt.title('FAST处理'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img3), plt.title('FAST处理-关闭非极大抑制'), plt.xticks([]), plt.yticks([])
plt.show()

# —————— BRIEF(二进制的鲁棒独立基本特征) ——————
# OpenCV中的BRIEF
# 下面的代码显示了借助CenSurE检测器对Brief描述符的计算。(在OpenCV中,CenSurE检测器称为STAR检测器)注意,您需要使用opencv contrib)才能使用它。
img = cv.imread('images/shudu.png', 0)
# 初始化FAST检测器
star = cv.xfeatures2d.StarDetector_create()
# 初始化BRIEF提取器
brief = cv.xfeatures2d.BriefDescriptorExtractor_create()
# 找到STAR的关键点
kp = star.detect(img, None)
# 计算BRIEF的描述符
kp, des = brief.compute(img, kp)
print(brief.descriptorSize())
print(des.shape)

# —————— BRIEF(二进制的鲁棒独立基本特征) ——————
# OpenCV中的ORB
img = cv.imread('images/watermark.bmp', 0)
# 初始化ORB检测器
orb = cv.ORB_create()
# 用ORB寻找关键点
kp = orb.detect(img, None)
# 用ORB计算描述符
kp, des = orb.compute(img, kp)
# 仅绘制关键点的位置,而不绘制大小和方向
img2 = cv.drawKeypoints(img, kp, None, color=(0, 255, 0), flags=0)
plt.imshow(img2), plt.show()

# —————— 特征匹配 ——————
# 1 使用ORB描述符进行Brute-Force匹配
img1 = cv.imread('images/cup_queryImage.jpg', cv.IMREAD_GRAYSCALE)  # 索引图像
img2 = cv.imread('images/cup_trainImage.jpg', cv.IMREAD_GRAYSCALE)  # 训练图像
# 初始化ORB检测器
orb = cv.ORB_create()
# 基于ORB找到关键点和检测器
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 创建BF匹配器的对象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)  # 匹配描述符.
matches = bf.match(des1, des2)  # 获取两个图像中的最佳匹配。
matches = sorted(matches, key=lambda x: x.distance)  # 我们按照距离的升序对它们进行排序,以使最佳匹配(低距离)排在前面。
img3 = cv.drawMatches(img1, kp1, img2, kp2, matches[:10], None,
                      flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)  # 绘制前10的匹配项
plt.imshow(img3), plt.title("使用ORB描述符进行Brute-Force匹配")
plt.show()
# 2 带有SIFT描述符和比例测试的Brute-Force匹配
img1 = cv.imread('images/cup_queryImage.jpg', cv.IMREAD_GRAYSCALE)  # 索引图像
img2 = cv.imread('images/cup_trainImage.jpg', cv.IMREAD_GRAYSCALE)  # 训练图像
# 初始化SIFT描述符
sift = cv.SIFT_create()
# 基于SIFT找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 默认参数初始化BF匹配器
bf = cv.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# 应用比例测试
good = []
for m, n in matches:
    if m.distance < 0.75 * n.distance:
        good.append([m])
# cv.drawMatchesKnn将列表作为匹配项。
img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3), plt.show()

# 3 基于匹配器的FLANN
img1 = cv.imread('images/cup_queryImage.jpg', cv.IMREAD_GRAYSCALE)  # 索引图像
img2 = cv.imread('images/cup_trainImage.jpg', cv.IMREAD_GRAYSCALE)  # 训练图像
# 初始化SIFT描述符
sift = cv.SIFT_create()
# 基于SIFT找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN的参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)  # 或传递一个空字典
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# 只需要绘制好匹配项,因此创建一个掩码
matchesMask = [[0, 0] for i in range(len(matches))]
# 根据Lowe的论文进行比例测试
for i, (m, n) in enumerate(matches):
    if m.distance < 0.7 * n.distance:
        matchesMask[i] = [1, 0]
draw_params = dict(matchColor=(0, 255, 0),
                   singlePointColor=(255, 0, 0),
                   matchesMask=matchesMask,
                   flags=cv.DrawMatchesFlags_DEFAULT)
img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)
plt.imshow(img3, ), plt.show()

# —————— 特征匹配 ——————
# 使用calib3d模块中的函数,即**cv.findHomography**()。如果我们从两个图像中传递点集,它将找到该对象的透视变换。然后,我们可以使用**cv.perspectiveTransform**()查找对象。找到转换至少需要四个正确的点。
# 匹配时可能会出现一些可能影响结果的错误。为了解决这个问题,算法使用RANSAC或LEAST_MEDIAN(可以由标志决定)。因此,提供正确估计的良好匹配称为“内部点”,其余的称为“外部点”。cv.findHomography()返回指定内部和外部点的掩码。
MIN_MATCH_COUNT = 10
img1 = cv.imread('images/cup_queryImage.jpg', cv.IMREAD_GRAYSCALE)  # 索引图像
img2 = cv.imread('images/cup_trainImage.jpg', cv.IMREAD_GRAYSCALE)  # 训练图像
# 初始化SIFT检测器
sift = cv.SIFT_create()
# 用SIFT找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# #根据Lowe的比率测试存储所有符合条件的匹配项。
good = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        good.append(m)

if len(good) > MIN_MATCH_COUNT:
    src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
    dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
    matchesMask = mask.ravel().tolist()
    h, w = img1.shape
    pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
    dst = cv.perspectiveTransform(pts, M)
    img2 = cv.polylines(img2, [np.int32(dst)], True, 255, 3, cv.LINE_AA)
else:
    print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
    matchesMask = None
draw_params = dict(matchColor=(0, 255, 0),  # 用绿色绘制匹配
                   singlePointColor=None,
                   matchesMask=matchesMask,  # 只绘制内部点
                   flags=2)
img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params)
plt.imshow(img3, 'gray'), plt.show()

啊~这图片怎么有水印啊!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Zotero 超好用插件的下载链接及配置方法(PDF-translate/ZotFile/茉莉花/Zotero Scihub)

目录 前言插件安装方法插件一&#xff1a;文献翻译插件&#xff08;pdf-translate&#xff09;插件二&#xff1a;文献附件管理&#xff08;ZotFile&#xff09;插件三&#xff1a;中文文献插件&#xff08;茉莉花&#xff09;插件四&#xff1a;Sci-Hub 自动下载文献&#xff…

学习使用php实现汉字验证码

学习使用php实现汉字验证码 <?php //开启session &#xff0c;方便验证 session_start(); //创建背景画布 $image imagecreatetruecolor(200, 60); $background imagecolorallocate($image, 255, 255, 255); imagefill($image, 0, 0, $background);//创建背景画布 for ($…

Mac-Java开发环境安装(JDK和Maven)

JDK安装 1、访问oracle官网&#xff0c;下载jdk 点击下载链接&#xff1a;https://www.oracle.com/java/technologies/downloads/#java11-mac 选择Mac版本&#xff0c;下载dmg 打勾点击下载&#xff0c;跳转登陆&#xff0c;没有就注册&#xff0c;输入账号密码即可下载成功…

Ubuntu20.04操作系统安装及重中之重:系统分区

最近因为学习原因&#xff0c;需要将电脑设置为双系统&#xff0c;在windows10的系统下去安装Ubuntu操作系统。本来看网上相关的安装教程蛮多的&#xff0c;以为比较简单&#xff0c;结果一路过五关斩六将&#xff0c;坑的七零八落的&#xff0c;折腾了好久&#xff0c;才算安装…

什么是文件安全

文件安全就是通过实施严格的访问控制措施和完美的权限卫生来保护您的业务关键信息不被窥探&#xff0c;除了启用和监控安全访问控制外&#xff0c;整理数据存储在保护文件方面也起着重要作用。通过清除旧的、过时的和其他垃圾文件来定期优化文件存储&#xff0c;以专注于关键业…

超好用的IDEA插件推荐,写完代码直接调试接口

Apipost推出IDEA插件非常省时高效&#xff0c;写完代码直接可以进行调试&#xff0c;而且支持生成接口文档&#xff0c;真是后端神器啊&#xff01; 可以点击下方链接安装更新或在插件商店中搜索安装 下载链接&#xff1a;https://plugins.jetbrains.com/plugin/22676-apipos…

linux安装apache并配置userid站点

目录 一、linux安装apache的方式 1、安装wget 2、下载CentOS 7的repo文件 3、更新镜像源 二、安装apache 1.通过命令直接安装apache(linux的软件包为httpd) 2.启动httpd服务 3.访问一下 三、apache配置文件 1.主配置文件 2.修改根目录 3.修改下端口 4.apache的工作…

C++标准模板(STL)- 类型支持 (类型特性,is_pointer,is_lvalue_reference,is_rvalue_reference)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实…

CMake基础【学习笔记(八)】

声明此博客为转载 CMake基础 文章目录 CMake基础一、准备知识1.1 C的编译过程1.2 静态链接库和动态链接库1.3 为什么需要CMake1.3.1 g 命令行编译1.3.2 CMake简介 二、CMake基础知识2.1 安装2.2 第一个CMake例子2.3 语法基础2.3.1 指定版本2.3.2 设置项目2.3.3 添加可执行文件…

从入门到大牛,JMeter接口测试+接口自动化测试(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在进行接口测试、…

符号执行初识

一、符号执行概念 符号执行&#xff08;Symbolic Execution&#xff09;是一种程序分析技术&#xff0c;它 可以通过分析程序来得到让特定代码区域执行的输入。 符号执行的 目的 是在给定的时间内&#xff0c; 生成一组输入&#xff0c;并通过这些输入尽可能多的探索执行路径。…

C++ Qt 学习(一):Qt 入门

Qt6 安装教程 0. 基础知识 0.1 qmake 和 cmake 对比 qmake&#xff1a;qt 独有的代码构建工具cmake&#xff1a;C 通用的代码构建工具&#xff0c;绝大部分 C 开源项目都使用 cmake 管理代码qt 项目&#xff0c;没有特殊要求&#xff0c;使用 qmake 即可 0.2 Qt 3 个窗口类的…

MATLAB野外观测站生态气象数据处理分析实践应用

1.基于MATLAB语言 2.以实践案例为主&#xff0c;提供所有代码 3.原理与操作结合 4.布置作业&#xff0c;答疑与拓展 示意图&#xff1a; 以野外观测站高频时序生态气象数据为例&#xff0c;基于MATLAB开展上机操作&#xff1a; 1.不同生态气象要素文件的数据读写与批处理实现 …

微信小程序自定义弹窗阻止滑动冒泡catchtouchmove之后弹窗内部内容无法滑动

自定义弹窗 如图所示&#xff1a; 自定义弹窗内部有带滚动条的盒子区域 问题&#xff1a; 在盒子上滑动&#xff0c;页面如果超出一屏的话&#xff0c;也会跟着一起上下滚动 解决方案&#xff1a;给自定义弹窗 添加 catchtouchmove 事件&#xff0c;阻止冒泡即可 网上不少…

Linux 安装 Redis7.x

Linux 安装 Redis7.x 下载redis7检查linux版本检查是否有 gcc什么是 gcc查看 gcc 是否有安装 安装 redis7查看默认安装目录启动服务连接服务服务关闭Redis的删除卸载Redis数据类型 下载redis7 下载地址&#xff1a;https://download.redis.io/releases/ 检查linux版本 [root…

Oracle JDK 和OpenJDK两者有什么异同点

Oracle JDK 和 OpenJDK 是两种不同版本的 Java Development Kit&#xff08;Java 开发工具包&#xff09;&#xff0c;它们都提供了用于开发 Java 程序的一系列工具和库。以下是它们之间的一些主要异同点&#xff1a; 相同点&#xff1a; 功能&#xff1a;在大多数情况下&…

【C++入门 三】学习C++缺省参数 | 函数重载 | 引用

C入门 三 1.缺省参数1.1 缺省参数概念1.2 缺省参数分类 2. 函数重载2.1 函数重载概念2.2 C支持函数重载的原理--名字修饰(name Mangling) 3.引用3.1引用概念3.2引用特性3.3 常引用3.4 使用场景1. 做参数2. 做返回值 3.5 传值、传引用效率比较3.6引用和指针的区别 4.引用和指针的…

2023最新ChatGPT商业运营系统源码+支持GPT4/支持ai绘画+支持Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

Linux进程基础

文章目录 1.进程概念2.进程描述3.进程操作&#xff08;一&#xff09;3.1.进程查看3.2.进程获取3.3.进程终止3.4.进程创建 4.进程状态4.1.进程状态理论4.1.1.粗略理解4.1.2.深入理解 4.2.进程状态实现4.2.1.运行状态和浅度/深度睡眠4.2.2.暂停状态和停止并跟踪状态4.2.3.终止状…

19.4 Boost Asio 远程命令执行

命令执行机制的实现与原生套接字通信一致&#xff0c;仅仅只是在调用时采用了Boost通用接口&#xff0c;在服务端中我们通过封装实现一个run_command函数&#xff0c;该函数用于发送一个字符串命令&#xff0c;并循环等待接收客户端返回的字符串&#xff0c;当接收到结束标志go…
最新文章