一、简介
通过代码来加深对一些简单的图像处理知识的了解。
代码链接:图像处理基本知识 Image Processing | Kaggle
可以一键保存到自己的账户上运行,无需配置环境
二、数据集
Kaggle上的opencv-samples-images数据集
三、代码解读
首先导入相关的库
import numpy as np
import matplotlib.pyplot as plt
import cv2
3.1 图像锐化
读取数据集中的一张图片,并展示锐化后的图片
image = cv2.imread('/kaggle/input/opencv-samples-images/data/building.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) #BGR转化为RGB 因为Matplotlib使用RGB格式显示图像,而OpenCV以BGR格式读取图像。
plt.figure(figsize=(20, 20)) #宽度和高度为20英寸
plt.subplot(1, 2, 1) #1行2列子图,子图索引从1开始
plt.title("Original")
plt.imshow(image)
# 创建这样的核时,会将核中的值进行归一化处理,使得它们的总和为1。这样可以确保锐化操作不会导致图像的整体亮度增加或减少。
# 然而此时特定的锐化核的所有值的总和已经等于1了,无需进行归一化
# 创建一个3x3的NumPy数组,表示一个锐化核。在这个核中,中心元素的权重为9,周围的元素的权重为-1
kernel_sharpening = np.array([[-1,-1,-1],
[-1,9,-1],
[-1,-1,-1]])
# 这一行使用OpenCV的filter2D函数将锐化核应用于原始图像。-1参数指定输出图像应该与输入图像具有相同的深度。
sharpened = cv2.filter2D(image, -1, kernel_sharpening)
plt.subplot(1, 2, 2)
plt.title("Image Sharpening")
plt.imshow(sharpened)
plt.show()
输出:
3.2 二值化处理(全局阈值、自适应阈值、大津法)
图像二值化就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。二值图像每个像素只有两种取值:要么纯黑,要么纯白。由于二值图像数据足够简单,许多视觉算法都依赖二值图像。通过二值图像,能更好地分析物体的形状和轮廓。
image = cv2.imread('/kaggle/input/opencv-samples-images/Origin_of_Species.jpg', 0)# 0表示灰度图像
plt.figure(figsize=(30, 30))
plt.subplot(3, 2, 1)
plt.title("Original")
plt.imshow(image)
# 设定阈值为127,所有像素值小于127的像素将被设为0(黑色),大于等于127的像素将被设为255(白色)
# cv2.THRESH_BINARY:阈值处理类型,这里选择了二进制阈值处理
# 返回值是一个元组 (ret, thresh1),其中 ret 是一个浮点数,表示选择的阈值,thresh1 是阈值处理后的图像。
ret,thresh1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
plt.subplot(3, 2, 2)
plt.title("Threshold Binary") #阈值二进制
plt.imshow(thresh1)
# 模糊图像是去除噪声的好方法
# (3, 3):高斯核的大小。这个参数指定了高斯滤波器的大小,它决定了平滑的程度。在这个例子中,指定的核大小为一个 3x3 的矩阵。核的大小越大,平滑效果就越明显,但也可能会导致图像失去细节。
# 0:高斯函数的标准差(sigma)。这个参数决定了高斯函数的形状,它越大,高斯曲线越平缓,平滑效果越明显。
image = cv2.GaussianBlur(image, (3, 3), 0)
# 使用自适应阈值
# 255:新像素值的最大值。在这种情况下,如果像素值超过了计算得到的局部阈值,它们会被设置为255(白色)。
# cv2.ADAPTIVE_THRESH_MEAN_C:自适应阈值处理类型。这里选择了基于均值的自适应阈值处理。在这种方法中,每个像素的阈值会根据它周围的像素的均值来计算。
# cv2.THRESH_BINARY:阈值处理类型,这里选择了二进制阈值处理。在二进制阈值处理中,大于阈值的像素值设为255(白色),小于等于阈值的像素值设为0(黑色)。
# 3:邻域大小。这个参数指定了用于计算局部阈值的邻域大小,一般取奇数值。在这个例子中,邻域大小为3x3。
# 5:阈值的常数偏移量。这个参数用于调整局部阈值的值。在这个例子中,偏移量为5
thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 5)
plt.subplot(3, 2, 3)
plt.title("Adaptive Mean Thresholding")
plt.imshow(thresh)
# 0:指定的阈值。在这里设置为0,因为使用 Otsu 的方法时,这个参数实际上不会被使用到。
# 255:新像素值的最大值。在这种情况下,如果像素值超过了计算得到的全局阈值,它们会被设置为255(白色)。
# cv2.THRESH_BINARY 表示二进制阈值处理,cv2.THRESH_OTSU 表示使用 Otsu 的方法计算全局阈值。
# 在这种方法中,Otsu 的方法会自动计算最佳的全局阈值,以使得类间方差最大化
_, th2 = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
plt.subplot(3, 2, 4)
plt.title("Otsu's Thresholding") # 大津法—最大类间方差法
plt.imshow(th2)
plt.subplot(3, 2, 5)
# 高斯滤波后的Otsu阈值
blur = cv2.GaussianBlur(image, (5,5), 0) # 同上面
_, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
plt.title("Guassian Otsu's Thresholding")
plt.imshow(th3)
plt.show()
部分输出:
3.3 腐蚀、膨胀、开运算、闭运算
腐蚀(erosion):将物体的边缘加以腐蚀
膨胀(dilation):将图像的轮廓加以膨胀
开运算(opening):先腐蚀后膨胀,用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积,消除物体表面的突起
闭运算(closing):对图像先膨胀,再腐蚀。闭操作的结果一般是可以将许多靠近的图块相连称为一个无突起的连通域
image = cv2.imread('/kaggle/input/opencv-samples-images/data/LinuxLogo.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(3, 2, 1)
plt.title("Original")
plt.imshow(image)
# 使用了NumPy库的 ones() 函数创建了一个 5x5 大小的矩阵,矩阵中的每个元素都初始化为1。这个矩阵代表了形态学操作的核(kernel)
kernel = np.ones((5,5), np.uint8)
# iterations = 1: 这是可选参数,指定腐蚀操作的迭代次数。每次迭代都会对图像进行一次腐蚀操作。默认值为 1,表示只进行一次腐蚀操作。如果需要更强烈的腐蚀效果,可以增加迭代次数。
erosion = cv2.erode(image, kernel, iterations = 1)
plt.subplot(3, 2, 2)
plt.title("Erosion") # 腐蚀
plt.imshow(erosion)
dilation = cv2.dilate(image, kernel, iterations = 1)
plt.subplot(3, 2, 3)
plt.title("Dilation") # 膨胀
plt.imshow(dilation)
# cv2.MORPH_OPEN: 这个参数指定了进行的形态学操作类型,即开运算。开运算是先进行腐蚀操作,然后进行膨胀操作的组合。开运算的效果通常是消除小的噪声并保持物体的形状,是一种常用的图像预处理技术。
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
plt.subplot(3, 2, 4)
plt.title("Opening")
plt.imshow(opening)
# cv2.MORPH_CLOSE: 这个参数指定了进行的形态学操作类型,即闭运算。闭运算是先进行膨胀操作,然后进行腐蚀操作的组合。闭运算的效果通常是填充物体内部的空洞,连接相邻的物体,以及平滑物体的边缘。
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
plt.subplot(3, 2, 5)
plt.title("Closing")
plt.imshow(closing)
部分输出:
3.4 图像边缘检测
image = cv2.imread('/kaggle/input/opencv-samples-images/data/fruits.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
height, width,_ = image.shape
# cv2.CV_64F: 这是Sobel滤波器的输出图像的数据类型,64位浮点数。
# 0, 1: 这是Sobel滤波器的水平和垂直方向的导数阶数。在这里,我们希望检测图像中的垂直边缘,因此水平方向的导数阶数为0,垂直方向的导数阶数为1。
# ksize=5: 这是Sobel滤波器的内核大小。内核大小决定了在边缘检测中使用的滤波器的大小。在这里,内核大小为5x5。
sobel_x = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=5)
sobel_y = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=5)
plt.figure(figsize=(20, 20))
plt.subplot(3, 2, 1)
plt.title("Original")
plt.imshow(image)
plt.subplot(3, 2, 2)
plt.title("Sobel X")
plt.imshow(sobel_x)
plt.subplot(3, 2, 3)
plt.title("Sobel Y")
plt.imshow(sobel_y)
sobel_OR = cv2.bitwise_or(sobel_x, sobel_y)
plt.subplot(3, 2, 4)
plt.title("sobel_OR")
plt.imshow(sobel_OR)
laplacian = cv2.Laplacian(image, cv2.CV_64F)
plt.subplot(3, 2, 5)
plt.title("Laplacian")
plt.imshow(laplacian)
# 梯度值低于50的被认为是非边缘,梯度值高于120的被认为是边缘
# 在threshold1和threshold2之间的值根据它们的强度如何“连接”而被分类为边缘或非边缘
canny = cv2.Canny(image, 50, 120)
plt.subplot(3, 2, 6)
plt.title("Canny")
plt.imshow(canny)
部分输出:
3.5 透视变换
image = cv2.imread('/kaggle/input/opencv-samples-images/scan.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(1, 2, 1)
plt.title("Original")
plt.imshow(image)
# 定义原始图像的四个角点坐标
points_A = np.float32([[320,15], [700,215], [85,610], [530,780]])
# 定义期望输出的四个角点坐标
points_B = np.float32([[0,0], [420,0], [0,594], [420,594]])
# cv2.getPerspectiveTransform(): 根据原始图像的四个角点坐标和期望输出的四个角点坐标计算透视变换矩阵
M = cv2.getPerspectiveTransform(points_A, points_B)
warped = cv2.warpPerspective(image, M, (420,594))
plt.subplot(1, 2, 2)
plt.title("warpPerspective")
plt.imshow(warped)
输出:
3.6 缩放,重新调整大小和插值
image = cv2.imread('/kaggle/input/opencv-samples-images/data/fruits.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
# 这里的None表示输出图像的大小是根据缩放因子 fx 和 fy 来确定的。这里 fx=0.75 和 fy=0.75 表示将图像沿水平和垂直方向各缩小为原始大小的3/4。
image_scaled = cv2.resize(image, None, fx=0.75, fy=0.75)
plt.subplot(2, 2, 2)
plt.title("Scaling - Linear Interpolation")
plt.imshow(image_scaled)
# interpolation=cv2.INTER_CUBIC参数指定了插值方法为立方插值
# 当需要对图像进行放大,并且要求得到平滑的结果时,可以使用立方插值。立方插值会根据周围的像素值来估计新像素的值,从而生成平滑的图像。它对于较大的放大因子通常效果较好。
img_scaled = cv2.resize(image, None, fx=2, fy=2, interpolation = cv2.INTER_CUBIC)
plt.subplot(2, 2, 3)
plt.title("Scaling - Cubic Interpolation")
plt.imshow(img_scaled)
# interpolation=cv2.INTER_AREA参数指定了插值方法为区域插值
# 当需要对图像进行缩小,并且要求处理速度比较快时,可以使用区域插值。区域插值简单地采用了原图像中的像素区域,并对其进行平均或加权平均,生成缩小后的图像
img_scaled = cv2.resize(image, (900, 400), interpolation = cv2.INTER_AREA)
plt.subplot(2, 2, 4)
plt.title("Scaling - Skewed Size")
plt.imshow(img_scaled)
部分输出:
3.7 图像金字塔
主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低
# 通过使用图像金字塔,可以在不同尺度上快速地对图像进行缩放,而且放大后的图像保持了原始图像的一些细节
image = cv2.imread('/kaggle/input/opencv-samples-images/data/butterfly.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
smaller = cv2.pyrDown(image) # 函数将原始图像按照高斯金字塔的方法进行降采样,从而将图像的尺寸缩小一半。
larger = cv2.pyrUp(smaller) # 将先前缩小后的图像进行上采样,从而将图像的尺寸放大一倍。这里采用的是图像的插值方法
plt.subplot(2, 2, 2)
plt.title("Smaller")
plt.imshow(smaller)
plt.subplot(2, 2, 3)
plt.title("Larger")
plt.imshow(larger)
输出:
3.8 图像剪裁
image = cv2.imread('/kaggle/input/opencv-samples-images/data/messi5.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
height, width = image.shape[:2]
# 将图像的高度和宽度各自乘以0.25,以确定裁剪区域的起始行和列
start_row, start_col = int(height * .25), int(width * .25)
# 将图像的高度和宽度各自乘以0.75,以确定裁剪区域的结束行和列
end_row, end_col = int(height * .75), int(width * .75)
# 通过在image数组上使用切片操作,裁剪出了位于起始行、起始列到结束行、结束列之间的区域
cropped = image[start_row:end_row , start_col:end_col]
plt.subplot(2, 2, 2)
plt.title("Cropped")
plt.imshow(cropped)
输出:
3.9 图像模糊
image = cv2.imread('/kaggle/input/opencv-samples-images/data/home.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
# 创建一个3*3的模糊核,全是1的矩阵,除以9表示进行归一化
kernel_3x3 = np.ones((3, 3), np.float32) / 9
# -1,则表示输出图像的深度与输入图像相同,否则可以通过指定其他深度来改变输出图像的深度
blurred = cv2.filter2D(image, -1, kernel_3x3)
plt.subplot(2, 2, 2)
plt.title("3x3 Kernel Blurring")
plt.imshow(blurred)
kernel_7x7 = np.ones((7, 7), np.float32) / 49
blurred2 = cv2.filter2D(image, -1, kernel_7x7)
plt.subplot(2, 2, 3)
plt.title("7x7 Kernel Blurring")
plt.imshow(blurred2)
部分输出:
3.10 图像轮廓识别
image = cv2.imread('/kaggle/input/opencv-samples-images/data/pic3.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
# 使用cv2.cvtColor()函数将RGB图像转换为灰度图像。
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# gray: 这是输入的灰度图像,即待检测边缘的图像。Canny边缘检测算法首先需要将图像转换为灰度图像,因为它是基于图像的强度梯度来检测边缘的
# 30: 这是Canny算法中的低阈值(Low threshold)。低阈值用于过滤掉弱边缘像素,只保留强度梯度大于低阈值的像素。低阈值通常设置为梯度强度的一部分,以确保检测到真正的边缘,并抑制噪声
# 200: 这是Canny算法中的高阈值(High threshold)。高阈值用于定义强边缘像素的梯度强度阈值,用于边缘连接。如果像素的梯度强度大于高阈值,则被认为是强边缘像素,如果小于高阈值但大于低阈值,则被认为是弱边缘像素。强边缘像素会直接被接受为边缘,而弱边缘像素只有在与强边缘像素连接时才被接受为边缘
edged = cv2.Canny(gray, 30, 200)
plt.subplot(2, 2, 2)
plt.title("Canny Edges")
plt.imshow(edged)
# cv2.RETR_EXTERNAL,表示只检测最外层的轮廓,即忽略任何内部的轮廓
# cv2.CHAIN_APPROX_NONE,表示不进行轮廓的近似,保留所有的轮廓点。这意味着得到的轮廓是由一系列连续的点组成的
# contours: 这是一个列表,其中每个元素都是一个轮廓,每个轮廓由一系列的点表示、
# hierarchy: 这是一个可选的输出参数,包含了轮廓的层次信息。在这个例子中没有使用该信息
contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
plt.subplot(2, 2, 3)
plt.title("Canny Edges After Contouring")
plt.imshow(edged)
print("Number of Contours found = " + str(len(contours)))
# -1: 这是要绘制的轮廓的索引。在这里,传入了 -1,表示绘制所有检测到的轮廓
# (0,255,0): 这是绘制轮廓的颜色。这里是一个 RGB 颜色元组
# 3: 这是绘制轮廓的线宽。在这里,线宽被设置为 3 像素
cv2.drawContours(image, contours, -1, (0,255,0), 3)
plt.subplot(2, 2, 4)
plt.title("Contours")
plt.imshow(image)
输出:
3.11 轮廓近似与凸包
image = cv2.imread('/kaggle/input/opencv-samples-images/house.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
orig_image = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# threshold() 函数来将灰度图像进行阈值处理,得到一个二值图像
# 127: 这是阈值。像素值大于等于阈值的像素将被设置为第四个参数(在这里是 255),像素值小于阈值的像素将被设置为第五个参数(在这里是 0)
# 255: 这是超过阈值时要将像素值设置为的值。在这里,超过阈值的像素值被设置为最大值255
# cv2.THRESH_BINARY_INV,表示将像素值大于等于阈值的像素设置为第三个参数(在这里是 255),像素值小于阈值的像素设置为0,并且进行反转(即黑白颠倒)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# 同10,注意此时查找原图像的轮廓而非经过Canny处理后的图像
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
for c in contours:
# # cv2.boundingRect() 函数计算轮廓 c 的边界矩形。返回的 (x,y) 是矩形的左上角坐标,w 是矩形的宽度,h 是矩形的高度
x,y,w,h = cv2.boundingRect(c)
print(x,y,w,h)
# cv2.rectangle() 函数在原始图像 orig_image 上绘制边界矩形。其中 (x,y) 是矩形的左上角坐标,(x+w,y+h) 是矩形的右下角坐标,(0,0,255) 是矩形的颜色(红色),2 是矩形的线宽。
cv2.rectangle(orig_image,(x,y),(x+w,y+h),(0,0,255),2)
plt.subplot(2, 2, 2)
plt.title("Bounding Rectangle") # 边界矩形
plt.imshow(orig_image)
cv2.waitKey(0)
for c in contours:
accuracy = 0.03 * cv2.arcLength(c, True) # 使用 cv2.arcLength() 函数计算轮廓的周长,然后乘以 0.03,得到了一个相对于轮廓周长的精度参数
approx = cv2.approxPolyDP(c, accuracy, True) # cv2.approxPolyDP() 函数将轮廓近似为具有较少顶点的多边形,其中 accuracy 是近似精度
cv2.drawContours(image, [approx], 0, (0, 255, 0), 2) # [approx] 是一个包含一个近似轮廓的列表,(0, 255, 0) 是轮廓的颜色(绿色),2 是轮廓的线宽
plt.subplot(2, 2, 3)
plt.title("Approx Poly DP") # Polygon Douglas-Peucker 算法是一种常用的轮廓近似算法,它通过保留轮廓的关键点来近似轮廓,从而减少了轮廓的点数,但尽可能地保持了原始轮廓的形状。这种近似处理使得轮廓更加简洁,减少了处理轮廓时的计算量,同时也有助于降低后续处理的复杂性
plt.imshow(image)
plt.show()
image = cv2.imread('/kaggle/input/opencv-samples-images/hand.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.figure(figsize=(20, 20))
plt.subplot(1, 2, 1)
plt.title("Original Image")
plt.imshow(image)
# 返回的 ret 是一个阈值,用于指示函数在调整阈值处理过程中所选择的阈值。thresh 是处理后的二值图像,其中像素值只有两个可能的取值,要么是0(黑色),要么是255(白色)
ret, thresh = cv2.threshold(gray, 176, 255, 0)
# 由于 findContours() 函数会修改输入图像,因此使用 copy() 方法来保留原始图像
# cv2.RETR_LIST,表示检测所有的轮廓,并将其存储在列表中,但不建立轮廓的层级关系
# cv2.CHAIN_APPROX_NONE,表示不进行轮廓的近似,保留所有的轮廓点
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 根据轮廓的面积对轮廓进行排序,并移除最大的外框轮廓
# 通过 cv2.contourArea 函数作为关键字来比较轮廓的面积大小
# reverse=False:这表示排序是升序的,即面积小的轮廓排在前面
# [:n]:这个切片操作从排序后的轮廓列表中选取了除了最后一个元素(最大的外框轮廓)之外的所有轮廓,存储在 contours 变量中
n = len(contours) - 1
contours = sorted(contours, key=cv2.contourArea, reverse=False)[:n]
for c in contours:
# # 找到其凸包,并将结果赋值给变量 hull。凸包是包围整个轮廓的最小凸多边形
hull = cv2.convexHull(c)
# [hull] 是一个包含凸包的列表。虽然我们只有一个凸包,但是 drawContours() 函数期望一个轮廓列表,所以我们将凸包放入一个列表中。
# 0 是绘制凸包的索引。在我们的情况下,只有一个凸包,所以索引为0。(0, 255, 0) 是绘制轮廓的颜色,这里是绿色。2 是绘制轮廓的线宽。
cv2.drawContours(image, [hull], 0, (0, 255, 0), 2)
plt.subplot(1, 2, 2)
plt.title("Convex Hull")
plt.imshow(image)
输出:
3.12 通过轮廓识别形状
image = cv2.imread('/kaggle/input/opencv-samples-images/someshapes.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.figure(figsize=(20, 20))
plt.subplot(2, 2, 1)
plt.title("Original")
plt.imshow(image)
# 将像素值大于等于 127 的像素设置为 255,小于 127 的像素设置为 0,并将结果保存在 thresh 变量中
# ret 返回的值通常与指定的阈值相同。但是在自适应阈值化等情况下,ret 可能会返回算法内部计算的阈值,这可能与用户指定的阈值不同。在代码中,如果不需要使用 ret,可以将其设为下划线 _,以表明这个返回值不会被使用
ret, thresh = cv2.threshold(gray, 127, 255, 1)
# thresh.copy(): 这是阈值化图像的副本。findContours 函数会修改传入的图像,因此我们通常会传入图像的副本,以保留原始图像。
# RETR_LIST 表示提取所有的轮廓,不建立轮廓的层级关系。
# HAIN_APPROX_NONE 表示不对轮廓进行近似,保留所有的轮廓点。
contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
for cnt in contours:
# 使用了之前提到的 approxPolyDP 函数,对每个轮廓进行多边形逼近,并将结果存储在 approx 变量中
# True:表示逼近的多边形是封闭的,即与原始轮廓相似
approx = cv2.approxPolyDP(cnt, 0.01*cv2.arcLength(cnt,True),True)
if len(approx) == 3:
shape_name = "Triangle"
# 0:轮廓列表中要绘制的轮廓的索引 (0,255,0):绘制轮廓的颜色(绿色) -1:表示填充轮廓
cv2.drawContours(image,[cnt],0,(0,255,0),-1)
# 使用 cv2.moments 函数计算轮廓的矩,其中 M['m10'] 和 M['m01'] 分别表示 x 和 y 方向上的一阶矩,M['m00'] 表示零阶矩。然后计算轮廓的质心坐标 (cx, cy)
M = cv2.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
# (cx-50, cy):文本的起始位置,这里设置为轮廓质心的 x 坐标减去50(使文本尽可能居中),y 坐标不变。
# cv2.FONT_HERSHEY_SIMPLEX:文本字体。1:文本的大小因子。(0, 0, 0):文本的颜色(黑色)。2:文本的线宽。
cv2.putText(image, shape_name, (cx-50, cy), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
elif len(approx) == 4:
x,y,w,h = cv2.boundingRect(cnt) # cv2.boundingRect()函数来获取轮廓cnt的边界框的左上角坐标(x, y)和宽度高度(w, h)
M = cv2.moments(cnt)
# 计算轮廓的质心坐标,即轮廓的中心点
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
# 判断边界框的宽度和高度之间的差是否小于等于3。如果差很小,我们认为它是一个正方形
if abs(w-h) <= 3:
shape_name = "Square" #正方形
cv2.drawContours(image, [cnt], 0, (0, 125 ,255), -1)
cv2.putText(image, shape_name, (cx-50, cy), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
else:
shape_name = "Rectangle" #长方形
cv2.drawContours(image, [cnt], 0, (0, 0, 255), -1)
cv2.putText(image, shape_name, (cx-50, cy), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
elif len(approx) == 10:
shape_name = "Star"
cv2.drawContours(image, [cnt], 0, (255, 255, 0), -1)
M = cv2.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.putText(image, shape_name, (cx-50, cy), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
elif len(approx) >= 15:
shape_name = "Circle"
cv2.drawContours(image, [cnt], 0, (0, 255, 255), -1)
M = cv2.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.putText(image, shape_name, (cx-50, cy), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
plt.subplot(2, 2, 2)
plt.title("Identifying Shapes")
plt.imshow(image)
输出:
3.13 使用霍夫线检测
image = cv2.imread('/kaggle/input/opencv-samples-images/data/sudoku.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 170, apertureSize = 3) # apertureSize = 3是Sobel算子的内核大小,用于计算图像梯度
plt.subplot(2, 2, 1)
plt.title("edges")
plt.imshow(edges)
# 1:表示 rho 的准确度,也就是表示以像素为单位的距离分辨率
# np.pi/180:表示 theta 的准确度,也就是表示以弧度为单位的角度分辨率。这里 np.pi/180 表示每一度的角度
# 200:这是霍夫变换中的阈值参数,它决定了最终的线条检测结果。只有当一个点所在的直线上超过了阈值个数的点,这条直线才会被检测到
lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
# lines 是一个包含检测到的直线参数的数组
for line in lines:
rho, theta = line[0] # 每个直线由 (rho, theta) 组成,其中 rho 是从原点到直线的垂线的距离,theta 是垂线与水平轴之间的角度
# 使用极坐标参数 rho 和 theta 计算直线上的一个点 (x0, y0)。
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
# 根据直线的极坐标参数,计算直线的两个端点 (x1, y1) 和 (x2, y2)。这里假设直线的长度为1000个像素
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
cv2.line(image, (x1, y1), (x2, y2), (255, 0, 0), 2)
plt.subplot(2, 2, 2)
plt.title("Hough Lines")
plt.imshow(image)
输出:
3.14 数圆和椭圆的个数
image = cv2.imread('/kaggle/input/opencv-samples-images/blobs.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20, 20))
# 创建了一个简单的斑点检测器对象 detector。这个检测器可以用来检测图像中的斑点或小的亮度区域,通常被称为 blob
detector = cv2.SimpleBlobDetector_create()
# 检测到的斑点信息会存储在 keypoints 中
keypoints = detector.detect(image)
# 创建了一个空白图像 blank,它是一个尺寸为 1x1 的黑色图像,用于在斑点周围绘制圆圈
# cv2.drawKeypoints() 函数将检测到的斑点绘制在原始图像 image 上。绘制的斑点以红色 (0, 0, 255) 圆圈表示
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 是一个标志,它指示函数绘制丰富的斑点,例如斑点的尺寸和响应值
blank = np.zeros((1,1))
blobs = cv2.drawKeypoints(image, keypoints, blank, (0,0,255),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
number_of_blobs = len(keypoints)
text = "Total Number of Blobs: " + str(len(keypoints))
# 参数依次为:要添加文本的图像,要添加的文本内容,文本的位置 (20, 550),字体类型,字体大小,文本颜色 (100, 0, 255),文本粗细
cv2.putText(blobs, text, (20, 550), cv2.FONT_HERSHEY_SIMPLEX, 1, (100, 0, 255), 2)
plt.subplot(2, 2, 1)
plt.title("Blobs using default parameters")
plt.imshow(blobs)
# 创建了一个参数对象 params,它用于设置斑点检测器的参数
params = cv2.SimpleBlobDetector_Params()
params.filterByArea = True # 表示我们要根据斑点的面积进行过滤
params.minArea = 100 # 表示斑点的最小面积为 100 个像素
params.filterByCircularity = True # 表示我们要根据斑点的圆度进行过滤
params.minCircularity = 0.9 #斑点的最小圆度为 0.9
params.filterByConvexity = False # 表示我们不会根据斑点的凸度进行过滤
params.minConvexity = 0.2 # 将 minConvexity 参数设置为 0.2,但由于我们将 filterByConvexity 设置为 False,因此此参数不会被使用
params.filterByInertia = True # 表示我们要根据斑点的惯性比(inertia ratio)进行过滤
params.minInertiaRatio = 0.01 # 表示斑点的最小惯性比为 0.01。惯性比是指斑点的形状在主轴和次轴之间的比率,取值范围为 0 到 1,其中 1 表示完美的圆形。因此,只有当斑点的惯性比大于等于 0.01 时,它才会被检测到
# 与使用默认参数不同,此处使用自己的参数来创建探测器
detector = cv2.SimpleBlobDetector_create(params)
keypoints = detector.detect(image)
blank = np.zeros((1,1))
blobs = cv2.drawKeypoints(image, keypoints, blank, (0,255,0),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
number_of_blobs = len(keypoints)
text = "Number of Circular Blobs: " + str(len(keypoints))
cv2.putText(blobs, text, (20, 550), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 100, 255), 2)
plt.subplot(2, 2, 2)
plt.title("Filtering Circular Blobs Only")
plt.imshow(blobs)
输出:
3.15 检测图像角点
image = cv2.imread('/kaggle/input/opencv-samples-images/data/chessboard.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 10))
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Harris 角点检测器需要输入的数据类型为 float32。因此,在使用 cv2.cornerHarris() 函数之前,必须将灰度图像的数据类型转换为 float32
gray = np.float32(gray)
harris_corners = cv2.cornerHarris(gray, 3, 3, 0.05) # 3 是指定 Sobel 算子的内核大小,用于计算图像梯度。这里的值是指 Sobel 算子的窗口大小,用于计算每个像素点的水平和垂直方向的梯度。3 是 Sobel 算子的内核大小的参数 ksize,它是指定 Sobel 算子的内核大小,用于计算图像梯度。这里的值是指 Sobel 算子的窗口大小,用于计算每个像素点的水平和垂直方向的梯度。0.05 是指定角点检测参数的值,它是 Harris 角点检测器中的自由参数 k,用于调整角点检测结果的灵敏度。较大的值会导致检测到的角点数量减少,较小的值会导致检测到的角点数量增多
# 使用膨胀操作对检测到的角点进行了扩大,使它们更加明显
kernel = np.ones((7,7),np.uint8) # 创建了一个大小为 (7, 7) 的矩形内核 kernel,用于膨胀操作
harris_corners = cv2.dilate(harris_corners, kernel, iterations = 10) # iterations 参数指定了膨胀操作的次数
# 对角点响应图进行了阈值化操作,以便找到最显著的角点并将它们标记在原始图像上
image[harris_corners > 0.025 * harris_corners.max() ] = [255, 127, 127] # 将角点响应图中最大响应值的 2.5% 作为阈值
plt.subplot(1, 1, 1)
plt.title("Harris Corners")
plt.imshow(image)
输出:
3.16 寻找人物
给定人物Waldo图像,在一幅图像中找到该人物
image = cv2.imread('/kaggle/input/opencv-samples-images/WaldoBeach.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(30, 30))
plt.subplot(2, 2, 1)
plt.title("Where is Waldo?")
plt.imshow(image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Load Template image
template = cv2.imread('/kaggle/input/opencv-samples-images/waldo.jpg',0) # 0表示灰度
# # cv2.matchTemplate() 函数执行模板匹配,它在输入图像上滑动模板图像,并计算每个位置的相似度得分。在这里,我们使用了 TM_CCOEFF 方法,它计算了模板与图像之间的相关性
result = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF)
# cv2.minMaxLoc() 函数用于查找得分矩阵中的最小值和最大值,并返回它们的位置。在这里,我们想找到得分矩阵中的最大值,因为最大值表示与模板最匹配的位置
# min_val:得分矩阵中的最小值 min_loc:得分矩阵中的最小值的位置,即相似度最低的位置的坐标
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
top_left = max_loc # 矩形的左上角的坐标。(484,262)
bottom_right = (top_left[0] + 50, top_left[1] + 50)
cv2.rectangle(image, top_left, bottom_right, (0,0,255), 5)
plt.subplot(2, 2, 2)
plt.title("Waldo")
plt.imshow(image)
输出:
3.17 背景消除
import cv2
import matplotlib.pyplot as plt
algo = 'MOG2' # algo 的值为 'MOG2',则选择了 MOG2 算法,否则选择了 KNN 算法
if algo == 'MOG2':
backSub = cv2.createBackgroundSubtractorMOG2()
else:
backSub = cv2.createBackgroundSubtractorKNN()
plt.figure(figsize=(20, 20))
frame = cv2.imread('/kaggle/input/opencv-samples-images/Background_Subtraction_Tutorial_frame.png')
# 调用背景减除器对象的 apply() 方法,将当前图像帧 frame 作为参数传递给它。这个方法会根据当前帧和之前帧的差异来提取前景对象,并生成前景掩码 fgMask。
fgMask = backSub.apply(frame)
plt.subplot(2, 2, 1)
plt.title("Frame")
plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
plt.subplot(2, 2, 2)
plt.title("FG Mask")
plt.imshow(cv2.cvtColor(fgMask, cv2.COLOR_BGR2RGB))
frame = cv2.imread('/kaggle/input/opencv-samples-images/Background_Subtraction_Tutorial_frame_1.png')
fgMask = backSub.apply(frame)
plt.subplot(2, 2, 3)
plt.title("Frame")
plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
plt.subplot(2, 2, 4)
plt.title("FG Mask")
plt.imshow(cv2.cvtColor(fgMask, cv2.COLOR_BGR2RGB))
输出:
3.18 图像扭曲
!pip install vcam
import cv2
import numpy as np
import math
from vcam import vcam,meshGen
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 20))
img = cv2.imread("/kaggle/input/opencv-samples-images/minions.jpg")
H,W = img.shape[:2] # 返回的是一个元组 (H, W, C),其中 H 表示图像的高度,W 表示图像的宽度,C 表示图像的通道数。由于我们只对高度和宽度感兴趣,所以使用了切片 [:2] 来获取前两个元素
# 创建了一个虚拟摄像机对象 c1
c1 = vcam(H=H,W=W)
# 创建了一个表面对象 plane,用于在虚拟摄像机视角下显示图像。
plane = meshGen(H,W)
# 这段代码用于生成一个镜子表面,其中每个 3D 点的 Z 坐标都根据特定的公式进行计算
plane.Z += 20*np.exp(-0.5*((plane.X*1.0/plane.W)/0.1)**2)/(0.1*np.sqrt(2*np.pi))
pts3d = plane.getPlane() # 用于获取更新后的表面上所有点的 3D 坐标
pts2d = c1.project(pts3d) # 将 3D 点投影到虚拟摄像机视角下,并获取了用于重新映射图像的映射函数
map_x,map_y = c1.getMaps(pts2d)
output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
plt.subplot(1, 2,1)
plt.title("Funny Mirror")
plt.imshow(cv2.cvtColor(np.hstack((img,output)), cv2.COLOR_BGR2RGB))
输出:
plt.figure(figsize=(20, 20))
img = cv2.imread("/kaggle/input/opencv-samples-images/minions.jpg")
H,W = img.shape[:2]
# Creating the virtual camera object
c1 = vcam(H=H,W=W)
# Creating the surface object
plane = meshGen(H,W)
plane.Z += 20*np.exp(-0.5*((plane.Y*1.0/plane.H)/0.1)**2)/(0.1*np.sqrt(2*np.pi))
pts3d = plane.getPlane()
pts2d = c1.project(pts3d)
map_x,map_y = c1.getMaps(pts2d)
output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
plt.subplot(1, 2,1)
plt.title("Funny Mirror")
plt.imshow(cv2.cvtColor(np.hstack((img,output)), cv2.COLOR_BGR2RGB))
输出:
plt.figure(figsize=(20, 20))
img = cv2.imread("/kaggle/input/opencv-samples-images/minions.jpg")
H,W = img.shape[:2]
c1 = vcam(H=H,W=W)
plane = meshGen(H,W)
plane.Z += 20*np.sin(2*np.pi*((plane.X-plane.W/4.0)/plane.W)) + 20*np.sin(2*np.pi*((plane.Y-plane.H/4.0)/plane.H))
pts3d = plane.getPlane()
pts2d = c1.project(pts3d)
map_x,map_y = c1.getMaps(pts2d)
output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
plt.subplot(1, 2,1)
plt.title("Funny Mirror")
plt.imshow(cv2.cvtColor(np.hstack((img,output)), cv2.COLOR_BGR2RGB))
输出:duqdu'q