当前位置: 首页 > news >正文

OpenCV 图像处理核心技术 (第二部分)

欢迎来到 OpenCV 图像处理的第二部分!在第一部分,我们学习了如何加载、显示、保存图像以及访问像素等基础知识。现在,我们将深入探索如何利用 OpenCV 提供的强大工具来修改和分析图像。

图像处理是计算机视觉领域的基石。通过对图像进行各种操作,我们可以改善图像质量、提取重要特征、准备图像用于更复杂的任务(如目标识别或图像分割)。

本部分将涵盖以下关键主题:

  1. 图像增强与滤波
    • 亮度与对比度调整
    • 图像平滑(均值滤波、高斯滤波)
    • 图像锐化(拉普拉斯算子)
  2. 图像形态学操作
    • 腐蚀与膨胀
    • 开运算与闭运算
    • 形态学梯度
  3. 边缘检测
    • Sobel 算子
    • Canny 边缘检测
  4. 实战:检测图像中的边缘

让我们开始吧!

1. 图像增强与滤波

图像增强旨在改善图像的视觉效果或为后续处理提供更好的输入。滤波则是通过对图像像素及其邻域像素进行计算,来达到平滑、锐化或提取特征的目的。

1.1 亮度与对比度调整

调整图像的亮度和对比度是最常见的图像增强操作之一。

  • 亮度 (Brightness): 控制图像的整体明暗程度。增加亮度就像给图像"加光"。
  • 对比度 (Contrast): 控制图像中明暗区域之间的差异程度。增加对比度会使亮区更亮,暗区更暗,图像看起来更"鲜明"。

数学上,简单的亮度和对比度调整可以通过线性变换实现:

OpenCV 提供了 cv2.convertScaleAbs() 函数来实现这个线性变换,并且会自动处理像素值超出 [0, 255] 范围的情况(截断到 0 或 255)。

Python

import cv2
import numpy as np# --- 练习 1.1: 调整图像亮度与对比度 ---# 1. 加载图像
# 请替换成你自己的图片路径
image_path = 'your_image.jpg'
try:image = cv2.imread(image_path)if image is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")# 使用一个虚拟图片代替,以便代码可以运行(实际操作中请替换)image = np.zeros((300, 500, 3), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)print(f"原始图像尺寸: {image.shape}")# 2. 定义调整参数
alpha = 1.5  # 对比度控制 (增益),大于1增强对比度
beta = 30    # 亮度控制 (偏置),正数增加亮度# 3. 应用线性变换进行亮度与对比度调整
# 注意: cv2.convertScaleAbs 会自动将结果转换为 uint8 并取绝对值 (虽然对于亮度/对比度调整,结果通常是非负的)
adjusted_image = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)# 4. 显示原始图像和调整后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Adjusted Image (alpha=1.5, beta=30)', adjusted_image)# 5. 尝试不同的参数组合
alpha_low_contrast = 0.7
beta_dark = -50
adjusted_low_contrast_dark = cv2.convertScaleAbs(image, alpha=alpha_low_contrast, beta=beta_dark)
cv2.imshow('Adjusted Image (alpha=0.7, beta=-50)', adjusted_low_contrast_dark)# 等待按键,然后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 1.1 完成 ---")

练习提示:

  • 尝试不同的 alphabeta 值,观察图像的变化。
  • alpha 可以是小数(例如 0.5 会降低对比度)。
  • beta 可以是负数(例如 -50 会降低亮度)。
1.2 图像平滑 (滤波)

图像平滑(也称为模糊)是一种常用的滤波技术,主要用于减少图像中的噪声或在进行边缘检测等操作前平滑图像。平滑的原理是使用一个核(Kernel)掩膜(Mask)与图像进行卷积。核是一个小矩阵,它在图像上滑动,在每个位置,将核的元素与对应的图像像素值相乘,然后将结果求和,得到中心像素的新值。

均值滤波 (Mean Filter)

Python

import cv2
import numpy as np# --- 练习 1.2.1: 均值滤波 ---# 1. 加载图像
image_path = 'your_image.jpg'
try:image = cv2.imread(image_path)if image is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")image = np.zeros((300, 500, 3), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)# 2. 定义核的大小 (必须是正奇数,如 (3, 3), (5, 5) 等)
ksize_mean = (5, 5)# 3. 应用均值滤波
# cv2.blur() 函数用于进行均值滤波
# 参数: src (输入图像), ksize (核大小)
mean_blurred_image = cv2.blur(image, ksize_mean)# 4. 显示原始图像和均值滤波后的图像
cv2.imshow('Original Image', image)
cv2.imshow(f'Mean Blurred Image (ksize={ksize_mean})', mean_blurred_image)# 5. 尝试不同的核大小
ksize_mean_large = (15, 15)
mean_blurred_image_large = cv2.blur(image, ksize_mean_large)
cv2.imshow(f'Mean Blurred Image (ksize={ksize_mean_large})', mean_blurred_image_large)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 1.2.1 完成 ---")

练习提示:

  • 尝试使用不同大小的核。核越大,平滑效果越明显,但图像细节丢失越多。
  • 均值滤波对去除“椒盐噪声”等随机噪声有一定效果。
高斯滤波 (Gaussian Filter)

高斯滤波是一种更常用的平滑滤波器。它使用一个符合高斯分布(正态分布)的核。与均值滤波不同,高斯核中的元素不是相等的,而是距离核中心越近的元素权重越大,距离越远权重越小。这使得高斯滤波在平滑图像的同时,能够更好地保留边缘信息。

高斯核的形状由标准差 σ 控制。σ 越大,核越“胖”,平滑范围越大。

Python

import cv2
import numpy as np# --- 练习 1.2.2: 高斯滤波 ---# 1. 加载图像
image_path = 'your_image.jpg'
try:image = cv2.imread(image_path)if image is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")image = np.zeros((300, 500, 3), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)# 2. 定义核的大小 (必须是正奇数) 和 高斯标准差 (sigmaX, sigmaY)
# sigmaX 和 sigmaY 分别表示高斯核在X和Y方向上的标准差
# 如果 sigmaY 为 0,则 sigmaY = sigmaX
# 如果 sigmaX 和 sigmaY 都为 0,则它们由核大小计算得出
ksize_gaussian = (5, 5)
sigma_x = 0 # 通常设置为 0,让 OpenCV 根据核大小自动计算# 3. 应用高斯滤波
# cv2.GaussianBlur() 函数用于进行高斯滤波
# 参数: src (输入图像), ksize (核大小), sigmaX, sigmaY (标准差), borderType (边界处理方式,通常不需要指定)
gaussian_blurred_image = cv2.GaussianBlur(image, ksize_gaussian, sigma_x)# 4. 显示原始图像和高斯滤波后的图像
cv2.imshow('Original Image', image)
cv2.imshow(f'Gaussian Blurred Image (ksize={ksize_gaussian}, sigmaX={sigma_x})', gaussian_blurred_image)# 5. 尝试不同的核大小和 sigmaX 值
ksize_gaussian_large = (15, 15)
sigma_x_large = 5 # 明确指定较大的标准差
gaussian_blurred_image_large = cv2.GaussianBlur(image, ksize_gaussian_large, sigma_x_large)
cv2.imshow(f'Gaussian Blurred Image (ksize={ksize_gaussian_large}, sigmaX={sigma_x_large})', gaussian_blurred_image_large)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 1.2.2 完成 ---")

练习提示:

  • 比较均值滤波和高斯滤波的效果,注意它们在边缘处的差异。
  • 尝试不同的 ksizesigmaX 值。sigmaX 对平滑效果影响很大。当 sigmaX 较大时,即使核较小,平滑效果也很明显。
1.3 图像锐化

与平滑相反,图像锐化旨在增强图像的边缘和细节,使图像看起来更清晰。锐化通常通过突出图像中的变化区域(如边缘)来实现。

一种常见的锐化方法是使用拉普拉斯算子 (Laplacian Operator)。拉普拉斯算子是二阶微分算子,它检测图像中灰度变化率最大的区域,这些区域通常对应于边缘。对图像应用拉普拉斯算子后得到的是边缘信息。

要实现图像锐化,可以将原始图像与拉普拉斯算子检测到的边缘信息进行结合。一个简单的锐化核是:

[ 0,  1,  0 ]
[ 1, -4,  1 ]
[ 0,  1,  0 ]

[-1, -1, -1]
[-1,  8, -1]
[-1, -1, -1]

使用这些核进行卷积可以直接得到锐化效果。

OpenCV 提供了 cv2.Laplacian() 函数来计算拉普拉斯算子,或者你可以使用 cv2.filter2D() 函数和自定义的锐化核进行卷积。对于初学者,使用 filter2D 配合锐化核可能更直观。

Python

import cv2
import numpy as np# --- 练习 1.3: 图像锐化 ---# 1. 加载图像 (灰度图通常更适合滤波操作)
# 如果你的图片是彩色图,可以先转换为灰度图
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY) # 转换为灰度图
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")# 使用一个虚拟灰度图片代替image = np.zeros((300, 500), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)print(f"灰度图像尺寸: {image.shape}")# 2. 定义锐化核 (一个常用的拉普拉斯锐化核)
sharpening_kernel = np.array([[0, -1, 0],[-1, 5, -1],[0, -1, 0]
], dtype=np.float32) # 使用 float32 类型# 3. 应用自定义核进行滤波 (锐化)
# cv2.filter2D() 函数使用自定义核对图像进行卷积
# 参数: src (输入图像), ddepth (输出图像深度,-1 表示与输入图像相同), kernel (核)
sharpened_image = cv2.filter2D(image, -1, sharpening_kernel)# 4. 显示原始灰度图像和锐化后的图像
cv2.imshow('Original Grayscale Image', image)
cv2.imshow('Sharpened Image (using filter2D)', sharpened_image)# 5. 使用 cv2.Laplacian 函数(仅获取边缘信息,不是直接锐化)
# 需要指定输出深度,通常使用 cv2.CV_64F 以避免溢出
laplacian = cv2.Laplacian(image, cv2.CV_64F)
# 将结果转换回 uint8
# 注意: 拉普拉斯结果可能有负值,需要取绝对值并缩放到 0-255 范围
laplacian_abs = cv2.convertScaleAbs(laplacian)
cv2.imshow('Laplacian Edge Information', laplacian_abs)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 1.3 完成 ---")

练习提示:

  • cv2.filter2D() 非常灵活,你可以尝试定义其他核(例如,一个简单的边缘检测核 np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]))来观察效果。
  • cv2.Laplacian() 直接计算的是图像的二阶导数,它更常用于边缘检测,而不是直接产生锐化后的图像(虽然可以通过与原图叠加实现锐化)。理解 filter2DLaplacian 的区别。
  • 注意 filter2Dddepth 参数,-1 表示输出与输入深度相同,如果核导致结果超出 0-255,可能会发生截断。对于一些滤波操作,可能需要将输入转换为更高的数据类型(如 float32)进行计算,然后再转换回 uint8。但在上面的锐化例子中,[-1, 5, -1] 核通常不会导致严重溢出,所以直接用 -1 输出深度是可行的。

2. 图像形态学操作

形态学操作是基于图像中特定形状(称为结构元素)的几何操作。它们主要应用于二值图像(只有黑白两种像素值,0 或 255),但也适用于灰度图像。形态学操作在处理图像中的对象形状、大小、连接等方面非常有用,例如去除噪声、分割独立元素、连接相邻元素、查找图像中的孔洞等。

形态学操作的关键在于结构元素。它是一个小矩阵,定义了在操作过程中如何探测或影响邻域像素。OpenCV 提供了 cv2.getStructuringElement() 函数来创建不同形状和大小的结构元素。

Python

import cv2
import numpy as np# --- 练习 2.0: 创建结构元素 ---# 1. 定义结构元素的形状和大小
# 形状: cv2.MORPH_RECT (矩形), cv2.MORPH_ELLIPSE (椭圆形), cv2.MORPH_CROSS (交叉形)
shape = cv2.MORPH_RECT
ksize = (5, 5) # 大小 (宽度, 高度)# 2. 创建结构元素
kernel_rect = cv2.getStructuringElement(shape, ksize)
print(f"矩形结构元素 ({ksize}):\n{kernel_rect}")shape_ellipse = cv2.MORPH_ELLIPSE
ksize_ellipse = (7, 7)
kernel_ellipse = cv2.getStructuringElement(shape_ellipse, ksize_ellipse)
print(f"\n椭圆形结构元素 ({ksize_ellipse}):\n{kernel_ellipse}")shape_cross = cv2.MORPH_CROSS
ksize_cross = (5, 5)
kernel_cross = cv2.getStructuringElement(shape_cross, ksize_cross)
print(f"\n交叉形结构元素 ({ksize_cross}):\n{kernel_cross}")# 注意:形态学操作通常用于二值图像,所以我们创建一个简单的二值图像作为例子
# 创建一个包含白色方块的黑色图像
binary_image = np.zeros((100, 100), dtype=np.uint8)
binary_image[20:80, 20:80] = 255# 添加一些小的噪声点 (白色点)
binary_image[10, 10] = 255
binary_image[90, 90] = 255
binary_image[10, 90] = 255
binary_image[90, 10] = 255# 添加一些小的孔洞 (黑色点)
binary_image[45:55, 45:55] = 0cv2.imshow('Original Binary Image', binary_image)
cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 2.0 完成 ---")

练习提示:

  • 理解不同形状和大小的结构元素。它们定义了形态学操作的“感受野”。
  • 注意形态学操作通常在二值图像上效果最明显。你需要先将图像转换为二值图像(例如使用 cv2.threshold())。
2.1 腐蚀 (Erosion)

腐蚀操作会“缩小”二值图像中的前景物体(白色区域)。它的工作原理是:对于输出图像的每个像素,如果其对应的输入图像像素的邻域中,完全被结构元素覆盖的区域都是前景像素,则输出像素为前景(白色);否则为背景(黑色)。

简单来说,如果结构元素在某个位置包含任何背景像素,并且其中心对应输入图像的一个前景像素,那么这个前景像素在输出中就会变成背景。这会导致前景物体的边缘被“腐蚀”掉。腐蚀可以用来去除图像中的小噪声点。

Python

import cv2
import numpy as np# --- 练习 2.1: 腐蚀操作 ---# 1. 加载或创建二值图像 (使用上面创建的带噪声和孔洞的二值图像)
binary_image = np.zeros((150, 150), dtype=np.uint8)
binary_image[30:120, 30:120] = 255 # 大方块
binary_image[10, 10] = 255 # 噪声点
binary_image[10, 130] = 255
binary_image[130, 10] = 255
binary_image[130, 130] = 255
binary_image[60:90, 60:90] = 0 # 孔洞# 2. 创建结构元素
kernel_erode = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 3. 应用腐蚀操作
# cv2.erode() 函数用于进行腐蚀
# 参数: src (输入图像), kernel (结构元素), iterations (迭代次数,可选,默认为1)
eroded_image = cv2.erode(binary_image, kernel_erode, iterations=1)# 4. 显示原始二值图像和腐蚀后的图像
cv2.imshow('Original Binary Image', binary_image)
cv2.imshow('Eroded Image (iterations=1)', eroded_image)# 5. 尝试多次迭代腐蚀
eroded_image_3_iter = cv2.erode(binary_image, kernel_erode, iterations=3)
cv2.imshow('Eroded Image (iterations=3)', eroded_image_3_iter)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 2.1 完成 ---")

练习提示:

  • 观察小的噪声点是如何被腐蚀掉的。
  • 观察大的白色方块的边缘是如何向内收缩的。
  • 尝试增加 iterations 参数的值,观察腐蚀效果如何增强。
2.2 膨胀 (Dilation)

膨胀操作会“放大”二值图像中的前景物体(白色区域)。它的工作原理是:对于输出图像的每个像素,如果其对应的输入图像像素的邻域中,至少有一个像素被结构元素覆盖的区域是前景像素,则输出像素为前景(白色);否则为背景(黑色)。

简单来说,如果结构元素的中心对应输入图像的一个背景像素,但该结构元素覆盖的区域内包含任何前景像素,那么这个背景像素在输出中就会变成前景。这会导致前景物体的边缘向外扩展。膨胀可以用来填补物体内部的小孔洞或连接相邻的物体。

Python

import cv2
import numpy as np# --- 练习 2.2: 膨胀操作 ---# 1. 加载或创建二值图像 (使用上面创建的带噪声和孔洞的二值图像)
binary_image = np.zeros((150, 150), dtype=np.uint8)
binary_image[30:120, 30:120] = 255 # 大方块
binary_image[10, 10] = 255 # 噪声点
binary_image[10, 130] = 255
binary_image[130, 10] = 255
binary_image[130, 130] = 255
binary_image[60:90, 60:90] = 0 # 孔洞# 2. 创建结构元素
kernel_dilate = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 3. 应用膨胀操作
# cv2.dilate() 函数用于进行膨胀
# 参数: src (输入图像), kernel (结构元素), iterations (迭代次数,可选,默认为1)
dilated_image = cv2.dilate(binary_image, kernel_dilate, iterations=1)# 4. 显示原始二值图像和膨胀后的图像
cv2.imshow('Original Binary Image', binary_image)
cv2.imshow('Dilated Image (iterations=1)', dilated_image)# 5. 尝试多次迭代膨胀
dilated_image_3_iter = cv2.dilate(binary_image, kernel_dilate, iterations=3)
cv2.imshow('Dilated Image (iterations=3)', dilated_image_3_iter)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 2.2 完成 ---")

练习提示:

  • 观察小的孔洞是如何被膨胀操作填补的。
  • 观察大的白色方块的边缘是如何向外扩展的。
  • 尝试增加 iterations 参数的值,观察膨胀效果如何增强。
2.3 开运算 (Opening)

开运算是先腐蚀后膨胀的操作。它常用于去除图像中的小噪声点(孤立的前景像素),同时基本保持较大物体的大小和形状不变。先腐蚀可以去除小的噪声点,再膨胀则可以恢复被腐蚀掉的较大物体。

Opening(Image)=Dilate(Erode(Image))

2.4 闭运算 (Closing)

闭运算是先膨胀后腐蚀的操作。它常用于填充物体内部的小孔洞或连接物体之间的狭窄间隙,同时基本保持物体的大小和形状不变。先膨胀可以填补孔洞和连接间隙,再腐蚀则可以恢复因膨胀而稍有增大的物体大小。

Closing(Image)=Erode(Dilate(Image))

OpenCV 提供了 cv2.morphologyEx() 函数,它可以执行包括开运算、闭运算在内的多种形态学操作。

Python

import cv2
import numpy as np# --- 练习 2.3 & 2.4: 开运算与闭运算 ---# 1. 加载或创建二值图像 (使用上面创建的带噪声和孔洞的二值图像)
binary_image = np.zeros((150, 150), dtype=np.uint8)
binary_image[30:120, 30:120] = 255 # 大方块
binary_image[10, 10] = 255 # 噪声点
binary_image[10, 130] = 255
binary_image[130, 10] = 255
binary_image[130, 130] = 255
binary_image[60:90, 60:90] = 0 # 孔洞# 2. 创建结构元素
kernel_morph = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 3. 应用开运算
# cv2.morphologyEx() 函数用于执行多种形态学操作
# 参数: src (输入图像), op (操作类型), kernel (结构元素), iterations (迭代次数,可选)
# cv2.MORPH_OPEN 表示开运算
opening_image = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel_morph)# 4. 应用闭运算
# cv2.MORPH_CLOSE 表示闭运算
closing_image = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel_morph)# 5. 显示原始、开运算和闭运算后的图像
cv2.imshow('Original Binary Image', binary_image)
cv2.imshow('Opening Operation', opening_image)
cv2.imshow('Closing Operation', closing_image)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 2.3 & 2.4 完成 ---")

练习提示:

  • 观察开运算如何有效地去除了小的噪声点,同时保留了较大的白色方块。
  • 观察闭运算如何填补了方块内部的孔洞。
  • 尝试使用不同大小和形状的结构元素,以及不同的迭代次数,看看它们如何影响开闭运算的效果。
2.5 形态学梯度 (Morphological Gradient)

形态学梯度是膨胀图像与腐蚀图像之间的差值。它会突显出物体的边缘轮廓。

MorphologicalGradient=Dilate(Image)−Erode(Image)

这个操作的结果通常可以看作是物体边界的“厚度”。

Python

import cv2
import numpy as np# --- 练习 2.5: 形态学梯度 ---# 1. 加载或创建二值图像 (使用上面创建的二值图像)
binary_image = np.zeros((150, 150), dtype=np.uint8)
binary_image[30:120, 30:120] = 255 # 大方块# 2. 创建结构元素
kernel_grad = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 3. 应用形态学梯度
# cv2.morphologyEx() 函数, op=cv2.MORPH_GRADIENT 表示形态学梯度
gradient_image = cv2.morphologyEx(binary_image, cv2.MORPH_GRADIENT, kernel_grad)# 4. 显示原始二值图像和形态学梯度图像
cv2.imshow('Original Binary Image', binary_image)
cv2.imshow('Morphological Gradient', gradient_image)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 2.5 完成 ---")

练习提示:

  • 形态学梯度结果是物体的边界区域,其宽度取决于结构元素的大小。
  • 将其与后面的边缘检测结果进行比较,理解它们的不同用途。形态学梯度侧重于根据物体形状来提取边界,而边缘检测侧重于像素灰度变化。

3. 边缘检测

边缘是图像中像素强度发生显著变化的区域。边缘携带着图像中物体形状、边界等重要信息,因此边缘检测是许多高级计算机视觉任务(如目标识别、图像分割)的基础步骤。

边缘检测通常涉及计算图像的梯度。梯度表示图像在某个方向上的变化率。在边缘位置,梯度的大小通常较大。

3.1 Sobel 算子

OpenCV 提供了 cv2.Sobel() 函数来计算 Sobel 梯度。

Python

import cv2
import numpy as np# --- 练习 3.1: Sobel 算子边缘检测 ---# 1. 加载图像并转换为灰度图 (边缘检测通常在灰度图上进行)
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")image = np.zeros((300, 500), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)# 2. 计算水平方向的 Sobel 梯度
# 参数: src (输入图像), ddepth (输出图像深度), dx (x方向导数阶数), dy (y方向导数阶数), ksize (Sobel核大小)
# 为了避免计算过程中出现负值截断,通常将输出深度设为 cv2.CV_64F
sobelx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)# 3. 计算垂直方向的 Sobel 梯度
sobely = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)# 4. 计算梯度幅值 (这里使用近似值: |Gx| + |Gy|)
# 将结果转换为 uint8,cv2.convertScaleAbs 会取绝对值并截断到 0-255
sobelx_abs = cv2.convertScaleAbs(sobelx)
sobely_abs = cv2.convertScaleAbs(sobely)
sobel_combined = cv2.addWeighted(sobelx_abs, 0.5, sobely_abs, 0.5, 0) # 简单相加作为合并# 也可以使用更精确的梯度幅值计算: sqrt(Gx^2 + Gy^2)
# combined_magnitude = np.sqrt(sobelx**2 + sobely**2)
# combined_magnitude = cv2.normalize(combined_magnitude, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) # 归一化到 0-255# 5. 显示结果
cv2.imshow('Original Grayscale Image', image)
cv2.imshow('Sobel X', sobelx_abs)
cv2.imshow('Sobel Y', sobely_abs)
cv2.imshow('Sobel Combined (|Gx| + |Gy|)', sobel_combined)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 3.1 完成 ---")

练习提示:

  • 观察 Sobel X 结果主要突出垂直方向的边缘,Sobel Y 结果主要突出水平方向的边缘。
  • 合并后的结果显示了各个方向的边缘。
  • 理解为什么需要使用 cv2.CV_64F 作为输出深度,以及 cv2.convertScaleAbs() 的作用。
  • 尝试不同的 ksize 值。

3.2 Canny 边缘检测

Canny 边缘检测是一种多阶段的边缘检测算法,被广泛认为是目前效果最好的边缘检测算法之一。它比 Sobel 算子更复杂,但能产生更细、更准确的边缘。Canny 算法主要包括以下步骤:

  1. 噪声抑制: 使用高斯滤波器平滑图像,去除噪声。
  2. 计算梯度: 计算图像在水平和垂直方向的梯度(通常使用 Sobel 算子)。
  3. 非极大值抑制 (Non-maximum Suppression): 细化边缘。在梯度方向上,只保留梯度局部最大值,抑制非最大值,使得边缘变细。
  4. 双阈值处理 (Double Thresholding): 定义两个阈值:高阈值 (high_threshold) 和低阈值 (low_threshold)。
    • 梯度值高于 high_threshold 的点被确定为强边缘
    • 梯度值低于 low_threshold 的点被排除。
    • 梯度值介于 low_thresholdhigh_threshold 之间的点被称为弱边缘,它们可能成为边缘,也可能不是。
  5. 边缘跟踪 (Edge Tracking by Hysteresis): 连接边缘。通过分析弱边缘的邻域,将与强边缘相连的弱边缘也视为边缘。

OpenCV 提供了 cv2.Canny() 函数来执行 Canny 边缘检测。

Python

import cv2
import numpy as np# --- 练习 3.2: Canny 边缘检测 ---# 1. 加载图像并转换为灰度图
image_path = 'your_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")image = np.zeros((300, 500), dtype=np.uint8)cv2.putText(image, "Image not found", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)cv2.putText(image, "Please replace 'your_image.jpg'", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)# 2. 定义双阈值
low_threshold = 50
high_threshold = 150 # 通常 high_threshold 是 low_threshold 的 2 到 3 倍# 3. 应用 Canny 边缘检测
# cv2.Canny() 函数执行 Canny 边缘检测
# 参数: src (8位输入图像), threshold1 (低阈值), threshold2 (高阈值),
#       apertureSize (Sobel算子大小,默认为3), L2gradient (是否使用更精确的L2范数计算梯度幅值,默认为False)
canny_edges = cv2.Canny(image, low_threshold, high_threshold)# 4. 显示原始灰度图像和 Canny 边缘图像
cv2.imshow('Original Grayscale Image', image)
cv2.imshow(f'Canny Edges (low={low_threshold}, high={high_threshold})', canny_edges)# 5. 尝试不同的阈值组合
canny_edges_loose = cv2.Canny(image, 30, 100) # 较低阈值,可能检测到更多弱边缘
canny_edges_strict = cv2.Canny(image, 100, 200) # 较高阈值,只检测到强边缘
cv2.imshow(f'Canny Edges (low=30, high=100)', canny_edges_loose)
cv2.imshow(f'Canny Edges (low=100, high=200)', canny_edges_strict)cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 练习 3.2 完成 ---")

练习提示:

  • Canny 边缘通常比 Sobel 边缘更细。
  • 双阈值的选择对结果影响很大。较低的阈值组合会检测到更多细节和噪声,较高的阈值组合则只保留强边缘。
  • 理解双阈值处理和边缘跟踪的工作原理。

4. 实战:检测图像中的边缘

现在,我们将结合学到的知识,在一个实际图像上进行边缘检测的实战练习。我们将加载一张图片,将其转换为灰度图,然后应用 Canny 边缘检测来找到物体的轮廓。

Python

import cv2
import numpy as np# --- 实战练习: 使用 Canny 边缘检测 ---# 1. 加载你的图像
# 请替换 'your_real_image.jpg' 为你的图片路径
image_path = 'your_real_image.jpg'
try:image_color = cv2.imread(image_path)if image_color is None:raise FileNotFoundError(f"图片文件未找到: {image_path}")print(f"成功加载彩色图像: {image_path}")
except FileNotFoundError as e:print(e)print("请确保你的图片文件存在并位于正确路径。")print("将使用一个内置的 OpenCV 示例图像进行演示。")# 如果图片加载失败,使用 OpenCV 的内置图像# 需要确保安装了 opencv-contrib-python 或手动下载图片# 例如,可以使用 './data/lena.jpg' 如果你有opencv_extra库的data文件夹# 或者简单创建一个模拟图像image_color = np.zeros((400, 600, 3), dtype=np.uint8)cv2.putText(image_color, "Placeholder Image", (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)# 2. 将图像转换为灰度图
gray_image = cv2.cvtColor(image_color, cv2.COLOR_BGR2GRAY)# 3. 应用 Canny 边缘检测
# 可以尝试不同的阈值来获得最佳效果
low_threshold = 100
high_threshold = 200
edges = cv2.Canny(gray_image, low_threshold, high_threshold)# 4. 显示原始图像和边缘检测结果
cv2.imshow('Original Image', image_color)
cv2.imshow('Grayscale Image', gray_image)
cv2.imshow(f'Canny Edges (low={low_threshold}, high={high_threshold})', edges)# 5. 等待按键,然后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()print("\n--- 实战练习 完成 ---")

实战提示:

  • 更换不同的图片文件进行测试。
  • 仔细调整 Canny 函数的 low_thresholdhigh_threshold 参数,观察边缘检测结果的变化。这是获得满意边缘检测结果的关键步骤。
  • 对于一些有噪声的图片,你可能需要在 Canny 之前先进行高斯平滑,虽然 cv2.Canny() 函数内部已经包含了高斯平滑的步骤,但有时外部的预处理平滑可以改善结果。

总结

在这一部分,我们深入学习了 OpenCV 中的核心图像处理技术,包括:

  • 图像增强和滤波: 通过调整亮度对比度改善视觉效果,使用均值滤波和高斯滤波进行平滑,使用拉普拉斯算子或自定义核进行锐化。
  • 形态学操作: 利用结构元素进行腐蚀、膨胀、开运算、闭运算和形态学梯度等操作,常用于二值图像处理和形状分析。
  • 边缘检测: 学习了 Sobel 算子和更强大的 Canny 算法,用于检测图像中像素强度变化显著的边缘区域。

相关文章:

  • Spring Boot 实现多种来源的 Zip 多层目录打包下载(本地文件HTTP混合)
  • 深入理解CSS显示模式与盒子模型
  • 麒麟(Kylin)系统下安装MySQL 8.4.5(离线版)
  • (32)VTK C++开发示例 ---背景纹理
  • C语言实现库函数strlen
  • 运维仙途 第1章 灵机突现探监控
  • Hbuilder 开发鸿蒙应用,打包成 hap 格式(并没有上架应用商店,只安装调试用)
  • HarmonyOS Next-DevEco Studio(5.0.2)无网络环境配置(详细教程)
  • Tailwind CSS实战技巧:从核心类到高效开发
  • HTML5 新增的主要标签整理
  • 基于C++的IOT网关和平台6:github项目ctGateway后台服务和数据模型
  • Vue3 Echarts 3D立方体柱状图实现教程
  • Github 热点项目 Qwen3 通义千问全面发布 新一代智能语言模型系统
  • Tomcat 服务频繁崩溃的排查与解决方法
  • 读论文笔记-LLaVA:Visual Instruction Tuning
  • 12.SpringDoc OpenAPI 功能介绍(用于生成API接口文档)
  • Qt QWebEngine应用和网页的交互
  • QCefView应用和网页的交互
  • HBuider中Uniapp去除顶部导航栏-小程序、H5、APP适用
  • scGPT-spatial:持续预训练scGPT用于空间转录组
  • 山东省委组织部办公室主任吴宪利已任德州市委常委、组织部部长
  • 辽宁辽阳市白塔区一饭店发生火灾,当地已启动应急响应机制
  • 陈文清:推进扫黑除恶常态化走深走实,有力回应人民群众对安居乐业的新期待
  • 铁路五一假期运输今日启动,预计发送旅客1.44亿人次
  • 4月人文社科联合书单|天文学家的椅子
  • 当AI开始深度思考,人类如何守住自己的慢思考能力?