python
复制代码
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()