(CV学习)opencv官网文档学习

在github上面也可以找到资源: https://github.com/opencv/opencv/tree/4.x

手写数据图片:https://github.com/opencv/opencv/blob/4.x/samples/data/digits.png
手写数据data:https://github.com/opencv/opencv/blob/4.x/samples/data/letter-recognition.data
人脸识别xml文件:https://github.com/opencv/opencv/tree/4.x/data/haarcascades

我将项目代码放在了gitee上:
https://gitee.com/wu_jingyi/opencv_learning/tree/master/getStart

整个项目结构:

在这里插入图片描述

文章最后有一些图片资源:
在这里插入图片描述

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('output/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('output/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()

7.视频分析

from __future__ import print_function
import cv2 as cv
import argparse

import numpy as np

# —————— 如何使用背景分离方法 ——————
parser = argparse.ArgumentParser(description='This program shows how to use background subtraction methods provided by \
                                              OpenCV. You can process both videos and images.')
parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='vtest.avi')
parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='MOG2')
args = parser.parse_args()
# cv::BackgroundSubtractor对象将用于生成前景掩码
if args.algo == 'MOG2':
    backSub = cv.createBackgroundSubtractorMOG2()
else:
    backSub = cv.createBackgroundSubtractorKNN()
capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input)) # 用于读取输入视频或输入图像序列
if not capture.isOpened:
    print('Unable to open: ' + args.input)
    exit(0)
while True:
    ret, frame = capture.read()
    if frame is None:
        break
    fgMask = backSub.apply(frame) #要更改用于更新背景模型的学习率,可以通过将参数传递给apply方法来设置特定的学习率。
    cv.rectangle(frame, (10, 2), (100, 20), (255, 255, 255), -1)     #获取帧号并将其写入当前帧
    cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),
               cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
    cv.imshow('Frame', frame)
    cv.imshow('FG Mask', fgMask)
    keyboard = cv.waitKey(30)
    if keyboard == 'q' or keyboard == 27:
        break

# —————— Meanshift和Camshift ——————
# OpenCV中的Meanshift
parser = argparse.ArgumentParser(description='This sample demonstrates the meanshift algorithm. \
                                              The example file can be downloaded from: \
                                              https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 视频的第一帧
ret,frame = cap.read()
# 设置窗口的初始位置
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)
# 设置初始ROI来追踪
roi = frame[y:y+h, x:x+w]
hsv_roi =  cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 设置终止条件,可以是10次迭代,也可以至少移动1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
    ret, frame = cap.read()
    if ret == True:
        hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
        dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
        # 应用meanshift来获取新位置
        ret, track_window = cv.meanShift(dst, track_window, term_crit)
        # 在图像上绘制
        x,y,w,h = track_window
        img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
        cv.imshow('img2',img2)
        k = cv.waitKey(30) & 0xff
        if k == 27:
            break
    else:
        break
# OpenCV中的Camshift
parser = argparse.ArgumentParser(description='This sample demonstrates the camshift algorithm. \
                                              The example file can be downloaded from: \
                                              https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 获取视频第一帧
ret,frame = cap.read()
# 设置初始窗口
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)
# 设置追踪的ROI窗口
roi = frame[y:y+h, x:x+w]
hsv_roi =  cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 设置终止条件,可以是10次迭代,有可以至少移动1个像素
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
    ret, frame = cap.read()
    if ret == True:
        hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
        dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
        # 应用camshift 到新位置
        ret, track_window = cv.CamShift(dst, track_window, term_crit)
        # 在图像上画出来
        pts = cv.boxPoints(ret)
        pts = np.int0(pts)
        img2 = cv.polylines(frame,[pts],True, 255,2)
        cv.imshow('img2',img2)
        k = cv.waitKey(30) & 0xff
        if k == 27:
            break
    else:
        break

# —————— 光流 ——————
# 1 OpenCV中的Lucas-Kanade
# 当我们上金字塔时,较小的动作将被删除,较大的动作将变为较小的动作。因此,通过在此处应用Lucas-Kanade,我们可以获得与尺度一致的光流。
# 在这里,我们创建一个简单的应用程序来跟踪视频中的某些点。为了确定点,我们使用**cv.goodFeaturesToTrack**()。我们采用第一帧,检测其中的一些Shi-Tomasi角点,然后使用Lucas-Kanade光流迭代地跟踪这些点。对于函数**cv.calcOpticalFlowPyrLK**(),我们传递前一帧,前一点和下一帧。它返回下一个点以及一些状态码,如果找到下一个点,状态码的值为1,否则为零。我们将这些下一个点迭代地传递为下一步中的上一个点。
parser = argparse.ArgumentParser(description='This sample demonstrates Lucas-Kanade Optical Flow calculation. \
                                              The example file can be downloaded from: \
                                              https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 用于ShiTomasi拐点检测的参数
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )
# lucas kanade光流参数
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
# 创建一些随机的颜色
color = np.random.randint(0,255,(100,3))
# 拍摄第一帧并在其中找到拐角
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# 创建用于作图的掩码图像
mask = np.zeros_like(old_frame)
while(1):
    ret,frame = cap.read()
    frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    # 计算光流
    p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    # 选择良好点
    good_new = p1[st==1]
    good_old = p0[st==1]
    # 绘制跟踪
    for i,(new,old) in enumerate(zip(good_new, good_old)):
        a,b = new.ravel()
        c,d = old.ravel()
        mask = cv.line(mask, (a,b),(c,d), color[i].tolist(), 2)
        frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1)
    img = cv.add(frame,mask)
    cv.imshow('frame',img)
    k = cv.waitKey(30) & 0xff
    if k == 27:
        break
    # 现在更新之前的帧和点
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)

# OpenCV中的密集光流
cap = cv.VideoCapture(cv.samples.findFile("vtest.avi"))
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
    ret, frame2 = cap.read()
    next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)
    flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
    mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])
    hsv[...,0] = ang*180/np.pi/2
    hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
    bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
    cv.imshow('frame2',bgr)
    k = cv.waitKey(30) & 0xff
    if k == 27:
        break
    elif k == ord('s'):
        cv.imwrite('output/opticalfb.png',frame2)
        cv.imwrite('output/opticalhsv.png',bgr)
    prvs = next

8.相机校准和3D重建

这个看文档我很是不理解,后面去B站上面学一下

# —————— 相机校准 ——————
import numpy as np
import cv2

# 准备标定板的规格信息
# 这里使用的是棋盘格,你可以更改这些参数以适应你的标定板
pattern_size = (9, 6)  # 内部角点的行数和列数
square_size = 1.0  # 棋盘格格子的尺寸(单位可以是任何你想要的)

# 创建空列表以保存检测到的角点和物理世界中的坐标
obj_points = []  # 3D 空间中的坐标
img_points = []  # 2D 图像中的坐标

# 构建3D点坐标
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size

# 打开摄像头
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 查找角点
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)

    if ret:
        obj_points.append(objp)
        img_points.append(corners)

        # 在图像上绘制角点
        cv2.drawChessboardCorners(frame, pattern_size, corners, ret)

    cv2.imshow('Calibration', frame)

    if cv2.waitKey(1) & 0xFF == 27:  # 按Esc键退出
        break

cap.release()
cv2.destroyAllWindows()

# 进行相机校准
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)

# 打印相机矩阵和畸变系数
print("相机矩阵:")
print(mtx)
print("畸变系数:")
print(dist)

# 保存相机矩阵和畸变系数
np.savez("calibration.npz", mtx=mtx, dist=dist)

9.机器学习

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  # 该语句解决图像中的“-”负号的乱码问题


# —————— k近邻(kNN)算法 ——————

# 包含(x,y)值的25个已知/训练数据的特征集
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
# 用数字0和1分别标记红色或蓝色
responses = np.random.randint(0,2,(25,1)).astype(np.float32)
# 取红色族并绘图
red = trainData[responses.ravel()==0] #从trainData中选择了那些对应类别为0的数据点,也就是红色类别。responses.ravel()用于将responses的形状从(25, 1)转换为(25,),以便进行索引操作。
plt.scatter(red[:,0],red[:,1],80,'r','^') #red[:,0]表示红色数据点的第一个特征,red[:,1]表示第二个特征,'80'表示点的大小,'r'表示红色,'^'表示使用三角形标记。
# 取蓝色族并绘图
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')
# 加入新样本
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)
print( "result:  {}\n".format(results) )
print( "neighbours:  {}\n".format(neighbours) )
print( "distance:  {}\n".format(dist) )
plt.show()



# —————— 使用OCR手写数据集运行KNN ——————
# 1 手写数字的OCR
img = cv.imread('images/digits.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 现在我们将图像分割为5000个单元格,每个单元格为20x20
cells = [np.hsplit(row, 100) for row in np.vsplit(gray, 50)]
# 使其成为一个Numpy数组。它的大小将是(50,100,20,20)
x = np.array(cells)
# 现在我们准备train_data和test_data。
train = x[:, :50].reshape(-1, 400).astype(np.float32)  # Size = (2500,400)
test = x[:, 50:100].reshape(-1, 400).astype(np.float32)  # Size = (2500,400)
# 为训练和测试数据创建标签
k = np.arange(10)
train_labels = np.repeat(k, 250)[:, np.newaxis]
test_labels = train_labels.copy()
# 初始化kNN,训练数据,然后使用k = 1的测试数据对其进行测试
knn = cv.ml.KNearest_create()
knn.train(train, cv.ml.ROW_SAMPLE, train_labels)
ret, result, neighbours, dist = knn.findNearest(test, k=5)
# 现在,我们检查分类的准确性
# 为此,将结果与test_labels进行比较,并检查哪个错误
matches = result == test_labels
correct = np.count_nonzero(matches)
accuracy = correct * 100.0 / result.size
print(accuracy)
# 保存数据
np.savez('output/knn_data.npz', train=train, train_labels=train_labels)
# 现在加载数据
with np.load('output/knn_data.npz') as data:
    print(data.files)
    train = data['train']
    train_labels = data['train_labels']  # 保存数据

# 2 英文字母的OCR
# 加载数据,转换器将字母转换为数字
data= np.loadtxt('data/letter-recognition.data', dtype= 'float32', delimiter = ',',
                    converters= {0: lambda ch: ord(ch)-ord('A')})
# 将数据分为两个,每个10000个以进行训练和测试
train, test = np.vsplit(data,2)
# 将火车数据和测试数据拆分为特征和响应
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])
# 初始化kNN, 分类, 测量准确性
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, result, neighbours, dist = knn.findNearest(testData, k=5)
correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print( accuracy )


# —————— SVM ——————
# 识别手写数字
# 在kNN中,我们直接使用像素强度作为特征向量。这次我们将使用定向梯度直方图(HOG)作为特征向量。
SZ=20
bin_n = 16 # Number of bins
affine_flags = cv.WARP_INVERSE_MAP|cv.INTER_LINEAR
# 在找到HOG之前,我们使用其二阶矩对图像进行偏斜校正。
# 因此,我们首先定义一个函数**deskew()**,该函数获取一个数字图像并将其校正。
def deskew(img):
    m = cv.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
    return img
# 我们找到了每个单元在X和Y方向上的Sobel导数。
# 然后在每个像素处找到它们的大小和梯度方向。该梯度被量化为16个整数值。
# 将此图像划分为四个子正方形。对于每个子正方形,计算权重大小方向的直方图(16个bin)。
# 因此,每个子正方形为你提供了一个包含16个值的向量。
# (四个子正方形的)四个这样的向量共同为我们提供了一个包含64个值的特征向量。这是我们用于训练数据的特征向量。
def hog(img):
    gx = cv.Sobel(img, cv.CV_32F, 1, 0)
    gy = cv.Sobel(img, cv.CV_32F, 0, 1)
    mag, ang = cv.cartToPolar(gx, gy)
    bins = np.int32(bin_n*ang/(2*np.pi))    # quantizing binvalues in (0...16)
    bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
    mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)     # hist is a 64 bit vector
    return hist
# 最后,与前面的情况一样,我们首先将大数据集拆分为单个单元格。
# 对于每个数字,保留250个单元用于训练数据,其余250个数据保留用于测试。
img = cv.imread('images/digits.jpg',0)
if img is None:
    raise Exception("we need the digits.png image from samples/data here !")
cells = [np.hsplit(row,100) for row in np.vsplit(img,50)]
# First half is trainData, remaining is testData
train_cells = [ i[:50] for i in cells ]
test_cells = [ i[50:] for i in cells]
deskewed = [list(map(deskew,row)) for row in train_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1,64)
responses = np.repeat(np.arange(10),250)[:,np.newaxis]
svm = cv.ml.SVM_create()
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(2.67)
svm.setGamma(5.383)
svm.train(trainData, cv.ml.ROW_SAMPLE, responses)
svm.save('output/svm_data.dat')
deskewed = [list(map(deskew,row)) for row in test_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
testData = np.float32(hogdata).reshape(-1,bin_n*4)
result = svm.predict(testData)[1]
mask = result==responses
correct = np.count_nonzero(mask)
print(correct*100.0/result.size)

# —————— OpenCV中的K-Means聚类 ——————
# cv.kmeans()函数进行数据聚类:
#   sample:它应该是**np.float32**数据类型,并且每个功能都应该放在单个列中。
#   nclusters(K):结束条件所需的簇数
#   criteria:这是迭代终止条件。满足此条件后,算法迭代将停止。实际上,它应该是3个参数的元组。它们是(type,max_iter,epsilon): a. 终止条件的类型。它具有3个标志,如下所示:
#       cv.TERM_CRITERIA_EPS-如果达到指定的精度epsilon,则停止算法迭代。
#       cv.TERM_CRITERIA_MAX_ITER-在指定的迭代次数max_iter之后停止算法。
#       cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER-当满足上述任何条件时,停止迭代。
# 输出参数:
#   紧凑度:它是每个点到其相应中心的平方距离的总和。
#   标签:这是标签数组(与上一篇文章中的“代码”相同),其中每个元素标记为“0”,“ 1” ..... 3.
#   中心:这是群集中心的阵列。
# 1. 单特征数据
x = np.random.randint(25, 100, 25)
y = np.random.randint(175, 255, 25)
z = np.hstack((x, y))
z = z.reshape((50, 1))
z = np.float32(z)
plt.hist(z, 256, [0, 256]), plt.show()
# 每当运行10次算法迭代或达到epsilon = 1.0的精度时,就停止算法并返回答案。
# 定义终止标准 = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# 设置标志
flags = cv.KMEANS_RANDOM_CENTERS
# 应用K均值
compactness, labels, centers = cv.kmeans(z, 2, None, criteria, 10, flags)
# 这为我们提供了紧凑性,标签和中心。在这种情况下,我得到的中心分别为60和207。标签的大小将与测试数据的大小相同,其中每个数据的质心都将标记为“ 0”,“ 1”,“ 2”等。现在,我们根据标签将数据分为不同的群集。
A = z[labels == 0]
B = z[labels == 1]
# 现在我们以红色绘制A,以蓝色绘制B,以黄色绘制其质心。
plt.hist(A, 256, [0, 256], color='r')
plt.hist(B, 256, [0, 256], color='b')
plt.hist(centers, 32, [0, 256], color='y')
plt.show()

# 2. 多特征数据
X = np.random.randint(25, 50, (25, 2))
Y = np.random.randint(60, 85, (25, 2))
Z = np.vstack((X, Y))
# 将数据转换未 np.float32
Z = np.float32(Z)
# 定义停止标准,应用K均值
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret, label, center = cv.kmeans(Z, 2, None, criteria, 10, cv.KMEANS_RANDOM_CENTERS)
# 现在分离数据, Note the flatten()
A = Z[label.ravel() == 0]
B = Z[label.ravel() == 1]
# 绘制数据
plt.scatter(A[:, 0], A[:, 1])
plt.scatter(B[:, 0], B[:, 1], c='r')
plt.scatter(center[:, 0], center[:, 1], s=80, c='y', marker='s')
plt.xlabel('Height'), plt.ylabel('Weight')
plt.show()

# 3.颜色量化
# 颜色量化是减少图像中颜色数量的过程。这样做的原因之一是减少内存。有时,某些设备可能会受到限制,因此只能产生有限数量的颜色。同样在那些情况下,执行颜色量化。在这里,我们使用k均值聚类进行颜色量化。
# 有3个特征,例如R,G,B。因此,我们需要将图像重塑为Mx3大小的数组(M是图像中的像素数)。在聚类之后,我们将质心值(也是R,G,B)应用于所有像素,以使生成的图像具有指定数量的颜色。再一次,我们需要将其重塑为原始图像的形状。
img = cv.imread("images/1.png")
Z = img.reshape((-1, 3))
# 将数据转化为np.float32
Z = np.float32(Z)
# 定义终止标准 聚类数并应用k均值
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
plt.figure("颜色量化")
plt.subplot(2, 2, 1), plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB)), plt.title(f'原圖'), plt.xticks([]), plt.yticks([])
for i in range(1, 4):
    K = 2 * i
    ret, label, center = cv.kmeans(Z, K, None, criteria, 10, cv.KMEANS_RANDOM_CENTERS)
    # 现在将数据转化为uint8, 并绘制原图像
    center = np.uint8(center)
    res = center[label.flatten()]
    res2 = res.reshape(img.shape)
    plt.subplot(2,2,i+1), plt.imshow(cv.cvtColor(res2,cv.COLOR_BGR2RGB)), plt.title(f'k={2*i}'), plt.xticks([]), plt.yticks([])
plt.show()


10.计算摄影学

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


# —————— 图像去噪 ——————
# OpenCV中的图像去噪
# OpenCV提供了此方法的四个变体。
#   cv.fastNlMeansDenoising()-处理单个灰度图像
#   cv.fastNlMeansDenoisingColored()-处理彩色图像。去除彩色图像中的高斯噪声或其他类型的噪声,同时保持图像的清晰度和颜色信息。
#   cv.fastNlMeansDenoisingMulti()-处理在短时间内捕获的图像序列(灰度图像).用于去除多通道彩色图像或多通道灰度图像中的噪声,同时保持图像的细节和结构。
#   cv.fastNlMeansDenoisingColoredMulti()-与上面相同,但用于彩色图像。
# 常用参数为:
#   - h:决定滤波器强度的参数。较高的h值可以更好地消除噪点,但同时也可以消除图像细节。(可以设为10)
#   - hForColorComponents:与颜色成分相关的参数。通常与 h 参数相同,但也可以进行单独调整以控制颜色噪声。
#   - templateWindowSize:应为奇数。(建议设为7)用于计算像素权重的窗口大小。较大的值考虑了更多的像素,但可能会导致过多的平滑。
#   - searchWindowSize:应为奇数。(建议设为21)用于搜索匹配块的窗口大小。较大的值可以找到更多的相似块,但也可能会增加计算时间。
# 1 cv.fastNlMeansDenoisingColored() 如上所述,它用于消除彩色图像中的噪点。(噪声可能是高斯的)
img = cv.imread('images/die.png')
# cv2.fastNlMeansDenoisingColored(源彩色图像src, 输出降噪后的彩色图像None, 滤波器强度h, hForColorComponents, 计算像素权重的窗口大小templateWindowSize, 搜索匹配块的窗口大小searchWindowSize)
dst = cv.fastNlMeansDenoisingColored(img,None,10,10,7,21)
plt.figure("OpenCV中的图像去噪-fastNlMeansDenoisingColored")
plt.subplot(121),plt.imshow(img),plt.title("Origin"),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title("fastNlMeansDenoisingColored"),plt.xticks([]),plt.yticks([])
plt.show()

# 2 cv.fastNlMeansDenoisingMulti(噪声帧列表srcImgs, 需要去噪的帧的索引imgToDenoiseIndex, 用于降噪的附近帧的数量temporalWindowSize, dst=None, h=3, hForColorComponents=10, templateWindowSize=7, searchWindowSize=21)
# srcImgs:噪声帧列表。
# imgToDenoiseIndex:指定我们需要去噪的帧,为此,我们在输入列表中传递帧的索引。
# temporalWindowSize:指定要用于降噪的附近帧的数量。应该很奇怪。在那种情况下,总共使用temporalWindowSize帧,其中中心帧是要被去噪的帧。例如,你传递了一个5帧的列表作为输入。令imgToDenoiseIndex = 2,temporalWindowSize =3。然后使用frame-1,frame-2和frame-3去噪frame-2。
cap = cv.VideoCapture('images/zaosheng.mp4')
# 创建5个帧的列表
img = [cap.read()[1] for i in range(5)]
# 将所有转化为灰度
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img]
# 将所有转化为float64
gray = [np.float64(i) for i in gray]
# 创建方差为25的噪声
noise = np.random.randn(*gray[1].shape)*10
# 在图像上添加噪声
noisy = [i+noise for i in gray]
# 转化为unit8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]
# 对第三帧进行降噪
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)
plt.figure("OpenCV中的图像去噪-fastNlMeansDenoisingMulti")
plt.subplot(131),plt.imshow(gray[2],'gray'),plt.title("Origin"),plt.xticks([]),plt.yticks([])
plt.subplot(132),plt.imshow(noisy[2],'gray'),plt.title("noisy"),plt.xticks([]),plt.yticks([])
plt.subplot(133),plt.imshow(dst,'gray'),plt.title("fastNlMeansDenoisingMulti"),plt.xticks([]),plt.yticks([])
plt.show()

# —————— 图像修补 ——————
# 基本思想很简单:用附近的像素替换那些不良区域,使其看起来和邻近的协调。
# 我们需要创建一个与输入图像大小相同的掩码,其中非零像素对应于要修复的区域。
img = cv.imread('images/messi.png')
mask = cv.imread('images/messi_mask.png',0)
INPAINT_TELEA = cv.inpaint(img,mask,3,cv.INPAINT_TELEA)
INPAINT_NS = cv.inpaint(img,mask,3,cv.INPAINT_NS)
plt.figure("图像修补")
plt.subplot(221),plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB)),plt.title("Origin"),plt.xticks([]),plt.yticks([])
plt.subplot(222),plt.imshow(mask,'gray'),plt.title("mask"),plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.imshow(cv.cvtColor(INPAINT_TELEA,cv.COLOR_BGR2RGB)),plt.title("cv.INPAINT_TELEA"),plt.xticks([]),plt.yticks([])
plt.subplot(224),plt.imshow(cv.cvtColor(INPAINT_NS,cv.COLOR_BGR2RGB)),plt.title("cv.INPAINT_NS"),plt.xticks([]),plt.yticks([])
plt.show()

# —————— 高动态范围 ——————
# 高动态范围成像(HDRI或HDR)是一种用于成像和摄影的技术,可以比标准数字成像或摄影技术重现更大的动态亮度范围。
# 当我们拍摄现实世界的照片时,明亮的区域可能会曝光过度,而黑暗的区域可能会曝光不足,因此我们无法一次拍摄所有细节。
# HDR成像适用于每个通道使用8位以上(通常为32位浮点值)的图像,从而允许更大的动态范围。
# 获取HDR图像的方法有多种,但是最常见的一种方法是使用以不同曝光值拍摄的场景照片。要综合这些曝光,了解相机的响应功能以及估算算法的功能非常有用。
# 合并HDR图像后,必须将其转换回8位才能在常规显示器上查看。此过程称为音调映射。
# 将曝光图像加载到列表中
img_fn = ["baoguang1.png", "baoguang2.png", "baoguang3.png", "baoguang4.png"]
img_list = [cv.imread(f"images/{fn}") for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)
# 将曝光合成HDR图像,这里提供了两种方法:merge_debevec 和 merge_robertson
# HDR图像的类型为float32,而不是uint8,因为它包含所有曝光图像的完整动态范围。
merge_debevec = cv.createMergeDebevec()
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())
merge_robertson = cv.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())
# 色调图HDR图像 我们将32位浮点HDR数据映射到[0..1]范围内。实际上,在某些情况下,该值可以大于1或小于0,因此请注意,我们稍后将必须裁剪数据以避免溢出。
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())
res_robertson = tonemap1.process(hdr_robertson.copy())
# 使用Mertens融合曝光 在这里,我们展示了一种替代算法,用于合并曝光图像,而我们不需要曝光时间。我们也不需要使用任何色调映射算法,因为Mertens算法已经为我们提供了[0..1]范围内的结果。
# 使用Mertens融合曝光
merge_mertens = cv.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
# 转为8-bit并保存:为了保存或显示结果,我们需要将数据转换为[0..255]范围内的8位整数
# 转化数据类型为8-bit并保存
res_debevec_8bit = np.clip(res_debevec * 255, 0, 255).astype('uint8')
res_robertson_8bit = np.clip(res_robertson * 255, 0, 255).astype('uint8')
res_mertens_8bit = np.clip(res_mertens * 255, 0, 255).astype('uint8')
# cv.imwrite("output/ldr_debevec.jpg", res_debevec_8bit)
# cv.imwrite("output/ldr_robertson.jpg", res_robertson_8bit)
# cv.imwrite("output/fusion_mertens.jpg", res_mertens_8bit)
plt.figure("高动态范围")
for i in range(1, 5):
    plt.subplot(2, 4, i), plt.imshow(cv.cvtColor(img_list[i - 1], cv.COLOR_BGR2RGB)), plt.title(
        f'exposure_time={exposure_times[i - 1]}'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 4, 5), plt.imshow(cv.cvtColor(res_debevec_8bit, cv.COLOR_BGR2RGB)), plt.title("res_debevec"), plt.xticks(
    []), plt.yticks([])
plt.subplot(2, 4, 6), plt.imshow(cv.cvtColor(res_robertson_8bit, cv.COLOR_BGR2RGB)), plt.title(
    "res_robertson"), plt.xticks([]), plt.yticks([])
plt.subplot(2, 4, 7), plt.imshow(cv.cvtColor(res_mertens_8bit, cv.COLOR_BGR2RGB)), plt.title("res_mertens"), plt.xticks(
    []), plt.yticks([])
plt.show()

# 估计相机响应函数
# 摄像机响应功能(CRF)使我们可以将场景辐射度与测量强度值联系起来。
# CRF在某些计算机视觉算法(包括HDR算法)中非常重要。
# 在这里,我们估计逆相机响应函数并将其用于HDR合并。
# 估计相机响应函数(CRF)
cal_debevec = cv.createCalibrateDebevec()  # 返回的对象是用于估计相机响应函数的对象,而不是包含实际响应函数数据的对象。因此,你需要从这些对象中提取响应函数数据,然后将其绘制出来。
crf_debevec = cal_debevec.process(img_list, times=exposure_times)
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy(), response=crf_debevec.copy())
hdr_debevec_8bit = np.clip(hdr_debevec * 255, 0, 255).astype('uint8')
# ldr_debevec = cv.xphoto.createTonemapDurand(gamma=2.2).process(hdr_debevec)# 可选:将HDR图像转换为LDR图像以进行显示
cal_robertson = cv.createCalibrateRobertson()
crf_robertson = cal_robertson.process(img_list, times=exposure_times)
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())
hdr_robertson_8bit = np.clip(hdr_robertson * 255, 0, 255).astype('uint8')
# ldr_robertson = cv.xphoto.createTonemapDurand(gamma=2.2).process(hdr_robertson)# 可选:将HDR图像转换为LDR图像以进行显示
# 显示相机响应函数曲线
x = np.arange(256)
# for i in range(hdr_robertson.shape[2]):
#     y = hdr_robertson[:, :, i].squeeze()
#     plt.plot(x, y, label=f'Channel {i + 1}')

plt.subplot(221)
plt.plot(x, crf_debevec[:, 0, 0], c='r', marker='o')
plt.plot(x, crf_debevec[:, 0, 1], c='g', marker='o')
plt.plot(x, crf_debevec[:, 0, 2], c='b', marker='o')
plt.title("crf_debevec")
plt.subplot(222)
plt.imshow(cv.cvtColor(hdr_debevec_8bit, cv.COLOR_BGR2RGB)),plt.xticks([]), plt.yticks([])
plt.title("hdr_debevec_8bit")
plt.subplot(223)
plt.plot(x, crf_robertson[:, 0, 0], c='r', marker='o')
plt.plot(x, crf_robertson[:, 0, 1], c='g', marker='o')
plt.plot(x, crf_robertson[:, 0, 2], c='b', marker='o')
plt.title("crf_robertson")
plt.subplot(224)
plt.imshow(cv.cvtColor(hdr_robertson_8bit, cv.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
plt.title("hdr_robertson_8bit")
plt.show()

11.目标检测


# —————— 级联分类器 ——————
# 1 OpenCV中的Haar-级联检测器
# 以下代码示例将使用预训练的Haar级联模型来检测图像中的面部和眼睛。首先,创建一个cv::CascadeClassifier并使用**cv::CascadeClassifier::load**方法加载必要的XML文件。然后,使用**cv::CascadeClassifier::detectMultiScale**方法完成检测,该方法返回检测到的脸部或眼睛的边界矩形。
from __future__ import print_function
import cv2 as cv
import argparse
def detectAndDisplay(frame):
    frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    frame_gray = cv.equalizeHist(frame_gray)
    #-- 检测面部
    faces = face_cascade.detectMultiScale(frame_gray)
    for (x,y,w,h) in faces:
        center = (x + w//2, y + h//2)
        frame = cv.ellipse(frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4)
        faceROI = frame_gray[y:y+h,x:x+w]
        #-- 在每张面部上检测眼睛
        eyes = eyes_cascade.detectMultiScale(faceROI)
        for (x2,y2,w2,h2) in eyes:
            eye_center = (x + x2 + w2//2, y + y2 + h2//2)
            radius = int(round((w2 + h2)*0.25))
            frame = cv.circle(frame, eye_center, radius, (255, 0, 0 ), 4)
    cv.imshow('Capture - Face detection', frame)
parser = argparse.ArgumentParser(description='Code for Cascade Classifier tutorial.')
parser.add_argument('--face_cascade', help='Path to face cascade.', default='data/haarcascade_frontalface_alt.xml') #这两个xml文件可以在github上下载
parser.add_argument('--eyes_cascade', help='Path to eyes cascade.', default='data/haarcascade_eye_tree_eyeglasses.xml')
parser.add_argument('--camera', help='Camera divide number.', type=int, default=0)
args = parser.parse_args()
face_cascade_name = args.face_cascade
eyes_cascade_name = args.eyes_cascade
face_cascade = cv.CascadeClassifier()
eyes_cascade = cv.CascadeClassifier()
#-- 1. 加载级联
if not face_cascade.load(cv.samples.findFile(face_cascade_name)):
    print('--(!)Error loading face cascade')
    exit(0)
if not eyes_cascade.load(cv.samples.findFile(eyes_cascade_name)):
    print('--(!)Error loading eyes cascade')
    exit(0)
camera_device = args.camera
#-- 2. 读取视频流
cap = cv.VideoCapture(camera_device)
if not cap.isOpened:
    print('--(!)Error opening video capture')
    exit(0)
while True:
    ret, frame = cap.read()
    if frame is None:
        print('--(!) No captured frame -- Break!')
        break
    detectAndDisplay(frame)
    if cv.waitKey(10) == 27:
        break

————————————————————————————————————————————————————

啊~这图片怎么有水印啊!

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
在这里插入图片描述

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值