图像预处理¶
机器人相机采集的原始图像很少能直接用于感知算法。光照变化、传感器噪声、无关的背景细节以及不一致的图像尺寸都会降低目标检测、特征匹配和位姿估计等下游任务的性能。**图像预处理**将原始相机数据转换为更干净、更一致的表示,从而使上层算法能够可靠地运行。
本教程涵盖每位机器人专业学生都应掌握的核心预处理技术——从色彩空间转换和滤波,到形态学操作和完整流水线。
学习目标¶
完成本教程后,你将能够:
- 在常见色彩空间(BGR、HSV、灰度)之间转换图像
- 应用空间滤波(高斯、中值、双边)并理解其权衡
- 使用直方图均衡化和 CLAHE 增强图像对比度
- 使用形态学操作清理二值掩码
- 应用几何变换(缩放、仿射、透视)
- 为机器人视觉任务构建完整的预处理流水线
前置要求¶
| 要求 | 详情 |
|---|---|
| Python | 3.8+ |
| 库 | opencv-python、numpy、matplotlib |
| 先修知识 | Python 基础、NumPy 数组索引 |
如需安装依赖:
1. 色彩空间¶
**色彩空间**定义了像素颜色的数值表示方式。为特定任务选择合适的色彩空间可以显著简化后续处理。
1.1 RGB / BGR¶
大多数库(PIL、matplotlib、scikit-image)使用 RGB 排序,但 OpenCV 默认使用 BGR。这一历史选择源于 OpenCV 早期开发时视频采集硬件常用的 BGR 字节序。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 加载图像(以 BGR 顺序返回)
img_bgr = cv2.imread("robot_workspace.jpg")
# 将 BGR 转换为 RGB,以便 matplotlib 正确显示
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].imshow(img_bgr) # 颜色错误——通道被交换
axes[0].set_title("原始 BGR(以 RGB 方式显示)")
axes[1].imshow(img_rgb) # 颜色正确
axes[1].set_title("转换后的 RGB")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
常见错误
在调用 plt.imshow() 之前忘记进行 BGR→RGB 转换是 OpenCV + matplotlib 工作流中最常见的新手错误。图像会呈现偏蓝/偏红的色调。
1.2 HSV / HSL¶
HSV(色相、饱和度、明度)将颜色(色相)与亮度(明度)分离,非常适合基于**颜色的分割**——例如,检测彩色球体或车道线标记,不受光照影响。
| 通道 | 范围(OpenCV) | 含义 |
|---|---|---|
| H(色相) | 0–179 | 色轮上的角度 |
| S(饱和度) | 0–255 | 颜色纯度 |
| V(明度) | 0–255 | 亮度 |
交互式 HSV 滑条演示¶
import cv2
import numpy as np
def nothing(x):
pass
img = cv2.imread("robot_workspace.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
cv2.namedWindow("Trackbars")
cv2.createTrackbar("L-H", "Trackbars", 0, 179, nothing)
cv2.createTrackbar("L-S", "Trackbars", 0, 255, nothing)
cv2.createTrackbar("L-V", "Trackbars", 0, 255, nothing)
cv2.createTrackbar("U-H", "Trackbars", 179, 179, nothing)
cv2.createTrackbar("U-S", "Trackbars", 255, 255, nothing)
cv2.createTrackbar("U-V", "Trackbars", 255, 255, nothing)
while True:
l_h = cv2.getTrackbarPos("L-H", "Trackbars")
l_s = cv2.getTrackbarPos("L-S", "Trackbars")
l_v = cv2.getTrackbarPos("L-V", "Trackbars")
u_h = cv2.getTrackbarPos("U-H", "Trackbars")
u_s = cv2.getTrackbarPos("U-S", "Trackbars")
u_v = cv2.getTrackbarPos("U-V", "Trackbars")
lower = np.array([l_h, l_s, l_v])
upper = np.array([u_h, u_s, u_v])
mask = cv2.inRange(hsv, lower, upper)
result = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("Original", img)
cv2.imshow("Mask", mask)
cv2.imshow("Result", result)
if cv2.waitKey(1) & 0xFF == 27: # 按 ESC 退出
break
cv2.destroyAllWindows()
静态 HSV 颜色检测示例¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
img_bgr = cv2.imread("robot_workspace.jpg")
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# 检测蓝色物体(OpenCV HSV 中蓝色的典型范围)
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
result = cv2.bitwise_and(img_bgr, img_bgr, mask=mask)
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
axes[0].set_title("原始图像")
axes[1].imshow(mask, cmap="gray")
axes[1].set_title("蓝色掩码")
axes[2].imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
axes[2].set_title("检测到的蓝色区域")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
1.3 灰度转换¶
许多算法(边缘检测、模板匹配、特征提取)在单通道灰度图像上运行。转换为灰度可以减少 3 倍数据量并简化计算。
import cv2
import matplotlib.pyplot as plt
img_bgr = cv2.imread("robot_workspace.jpg")
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
print(f"原始图像形状: {img_bgr.shape}") # (H, W, 3)
print(f"灰度图像形状: {gray.shape}") # (H, W)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
axes[0].set_title("彩色 (RGB)")
axes[1].imshow(gray, cmap="gray")
axes[1].set_title("灰度")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
2. 图像滤波¶
滤波(也称为*平滑*或*模糊*)用于抑制噪声和去除细节。滤波器的选择取决于噪声类型以及是否需要保留边缘。
2.1 高斯滤波¶
高斯滤波器使用钟形曲线(高斯)核将每个像素替换为其邻域的加权平均值。它对**高斯噪声**有效,是最常用的低通滤波器。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
# 使用递增的核大小应用高斯模糊
blur_3 = cv2.GaussianBlur(img, (3, 3), 0)
blur_7 = cv2.GaussianBlur(img, (7, 7), 0)
blur_15 = cv2.GaussianBlur(img, (15, 15), 0)
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for ax, im, title in zip(axes,
[img, blur_3, blur_7, blur_15],
["原始", "3×3", "7×7", "15×15"]):
ax.imshow(im, cmap="gray")
ax.set_title(f"高斯 {title}")
ax.axis("off")
plt.tight_layout()
plt.show()
核大小规则
- 必须是正奇数:3、5、7、……
- 核越大 → 模糊越强,计算越慢
- 第三个参数
0表示让 OpenCV 根据核大小自动计算 σ
2.2 中值滤波¶
中值滤波器**将每个像素替换为其邻域的中值。它特别擅长去除**椒盐噪声(随机黑白像素),同时比高斯滤波器更好地保留边缘。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
# 模拟椒盐噪声
def add_salt_pepper(image, amount=0.02):
noisy = image.copy()
num_salt = int(amount * image.size / 2)
# 盐噪声
coords = tuple(np.random.randint(0, d, num_salt) for d in image.shape)
noisy[coords] = 255
# 胡椒噪声
coords = tuple(np.random.randint(0, d, num_salt) for d in image.shape)
noisy[coords] = 0
return noisy
noisy = add_salt_pepper(img, amount=0.03)
median_3 = cv2.medianBlur(noisy, 3)
median_7 = cv2.medianBlur(noisy, 7)
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
for ax, im, title in zip(axes,
[noisy, median_3, median_7],
["含噪图像", "中值 3×3", "中值 7×7"]):
ax.imshow(im, cmap="gray")
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
2.3 双边滤波¶
双边滤波器**在平滑图像的同时**保留边缘。它在对邻域像素加权时同时考虑空间距离和强度差异——跨越边缘的像素贡献非常小。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
# cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
bilateral = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
gaussian = cv2.GaussianBlur(img, (9, 9), 0)
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, im, title in zip(axes,
[img, gaussian, bilateral],
["原始", "高斯 9×9", "双边 d=9"]):
ax.imshow(im, cmap="gray")
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
| 参数 | 含义 |
|---|---|
d |
像素邻域直径(使用 -1 可从 sigmaSpace 自动计算) |
sigmaColor |
值越大 → 邻域中混合的颜色越多 |
sigmaSpace |
值越大 → 距离更远的像素相互影响越大 |
2.4 滤波器对比¶
| 滤波器 | 最佳用途 | 保留边缘? | 速度 |
|---|---|---|---|
| 高斯 | 高斯/一般噪声 | 否 | 快 |
| 中值 | 椒盐噪声 | 一般 | 中 |
| 双边 | 保边平滑 | 是 | 慢 |
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
filters = {
"高斯 (5×5)": cv2.GaussianBlur(img, (5, 5), 0),
"中值 (5)": cv2.medianBlur(img, 5),
"双边": cv2.bilateralFilter(img, 5, 50, 50),
}
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
axes[0].imshow(img, cmap="gray")
axes[0].set_title("原始")
for ax, (name, im) in zip(axes[1:], filters.items()):
ax.imshow(im, cmap="gray")
ax.set_title(name)
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
3. 直方图均衡化¶
直方图均衡化将像素强度重新分布到完整的动态范围内,改善光照不足场景中的对比度——这在室内机器人环境中很常见。
3.1 基本直方图均衡化¶
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
equ = cv2.equalizeHist(img)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].imshow(img, cmap="gray")
axes[0, 0].set_title("原始")
axes[0, 1].imshow(equ, cmap="gray")
axes[0, 1].set_title("均衡化后")
axes[1, 0].hist(img.ravel(), bins=256, range=(0, 256))
axes[1, 0].set_title("原始直方图")
axes[1, 1].hist(equ.ravel(), bins=256, range=(0, 256))
axes[1, 1].set_title("均衡化直方图")
for ax in axes.flat:
if ax in axes[0]:
ax.axis("off")
plt.tight_layout()
plt.show()
3.2 CLAHE(对比度受限自适应直方图均衡化)¶
基本均衡化使用全局直方图,可能在均匀区域过度放大噪声。CLAHE 将图像分成小块,独立均衡化每个小块,并通过裁剪限制来防止噪声放大。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg", cv2.IMREAD_GRAYSCALE)
# 全局均衡化
equ = cv2.equalizeHist(img)
# CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
clahe_img = clahe.apply(img)
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, im, title in zip(axes,
[img, equ, clahe_img],
["原始", "全局均衡化", "CLAHE (clip=2.0)"]):
ax.imshow(im, cmap="gray")
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
何时使用 CLAHE
CLAHE 是机器人领域的首选方案,因为场景通常同时包含明亮和黑暗区域(例如,机器人从走廊进入阳光照射的房间)。它避免了全局均衡化的泛白效果。
4. 形态学操作¶
形态学操作基于形状处理二值(或灰度)图像。它们对于清理阈值化后的掩码至关重要——去除小噪声块、填充小孔以及分离接触的物体。
4.1 腐蚀与膨胀¶
**腐蚀**缩小亮区域:只有当核下的*所有*像素都为 1 时,该像素才变为 1。
**膨胀**扩大亮区域:只要核下的*任意*像素为 1,该像素就变为 1。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 创建合成二值图像用于演示
img = np.zeros((200, 200), dtype=np.uint8)
cv2.circle(img, (60, 60), 30, 255, -1)
cv2.circle(img, (140, 60), 30, 255, -1)
cv2.circle(img, (100, 140), 40, 255, -1)
cv2.rectangle(img, (20, 120), (50, 180), 255, -1)
# 添加噪声
noise = np.random.randint(0, 2, img.shape, dtype=np.uint8) * 255
img_noisy = cv2.bitwise_or(img, noise)
# 定义核
kernel = np.ones((5, 5), np.uint8)
eroded = cv2.erode(img_noisy, kernel, iterations=1)
dilated = cv2.dilate(img_noisy, kernel, iterations=1)
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
for ax, im, title in zip(axes,
[img_noisy, eroded, dilated],
["含噪二值图", "腐蚀 (5×5)", "膨胀 (5×5)"]):
ax.imshow(im, cmap="gray")
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
核的变化形式¶
import cv2
import numpy as np
# 矩形核
kernel_rect = np.ones((5, 5), np.uint8)
# 椭圆核
kernel_ellip = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
# 十字形核
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
print("矩形核:\n", kernel_rect)
print("椭圆核:\n", kernel_ellip)
print("十字核:\n", kernel_cross)
4.2 开运算与闭运算¶
- 开运算 = 先腐蚀后膨胀。去除小的亮噪声,同时保留较大的结构。
- 闭运算 = 先膨胀后腐蚀。填充亮物体内部的小暗孔。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = np.zeros((200, 200), dtype=np.uint8)
cv2.circle(img, (100, 100), 50, 255, -1)
# 添加噪声和孔洞
noise = np.random.randint(0, 2, img.shape, dtype=np.uint8) * 255
img_dirty = cv2.bitwise_or(img, noise)
kernel = np.ones((7, 7), np.uint8)
opened = cv2.morphologyEx(img_dirty, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(img_dirty, cv2.MORPH_CLOSE, kernel)
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
for ax, im, title in zip(axes,
[img_dirty, opened, closed],
["含噪二值图", "开运算(去噪)", "闭运算(填孔)"]):
ax.imshow(im, cmap="gray")
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
4.3 实际应用:噪声去除与孔洞填充¶
在机器人学中,对颜色检测到的物体进行阈值化后,掩码通常有噪声。典型的清理流程:
import cv2
import numpy as np
def clean_mask(mask, kernel_size=5, iterations=2):
"""使用形态学操作清理二值掩码。"""
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
# 步骤 1:去除小噪声(开运算)
cleaned = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=iterations)
# 步骤 2:填充小孔(闭运算)
cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel, iterations=iterations)
return cleaned
# 与颜色检测掩码配合使用:
# mask = cv2.inRange(hsv, lower_color, upper_color)
# clean = clean_mask(mask)
5. 图像变换¶
几何变换调整图像的位置、方向和大小。它们用于规范化神经网络输入、校正相机透视以及对齐拼接图像。
5.1 缩放与裁剪¶
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 缩放到精确尺寸
resized = cv2.resize(img_rgb, (320, 240))
# 按比例缩放
half = cv2.resize(img_rgb, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# 裁剪感兴趣区域 (y1:y2, x1:x2)
h, w = img_rgb.shape[:2]
cropped = img_rgb[h//4:3*h//4, w//4:3*w//4]
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, im, title in zip(axes,
[resized, half, cropped],
[f"缩放 {resized.shape[1]}×{resized.shape[0]}",
f"缩放50% {half.shape[1]}×{half.shape[0]}",
f"裁剪 {cropped.shape[1]}×{cropped.shape[0]}"]):
ax.imshow(im)
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
插值方法
cv2.INTER_AREA— 缩小图像时最佳(避免摩尔纹)cv2.INTER_LINEAR— 放大时的良好默认(双线性)cv2.INTER_CUBIC— 更高质量但更慢cv2.INTER_NEAREST— 最快,最近邻(用于掩码)
5.2 仿射变换¶
**仿射变换**保持平行线不变(平移、旋转、缩放、剪切)。你需要指定三对对应点。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w = img.shape[:2]
# 源点(原始图像中)
pts_src = np.float32([[50, 50], [200, 50], [50, 200]])
# 目标点(应映射到的位置)
pts_dst = np.float32([[10, 100], [200, 50], [100, 250]])
# 计算仿射矩阵
M = cv2.getAffineTransform(pts_src, pts_dst)
# 应用变换
affine_img = cv2.warpAffine(img_rgb, M, (w, h))
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].imshow(img_rgb)
axes[0].set_title("原始图像")
for pt in pts_src:
axes[0].plot(pt[0], pt[1], 'ro')
axes[1].imshow(affine_img)
axes[1].set_title("仿射变换")
for pt in pts_dst:
axes[1].plot(pt[0], pt[1], 'ro')
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
围绕某点旋转¶
import cv2
import numpy as np
img = cv2.imread("robot_workspace.jpg")
h, w = img.shape[:2]
# 围绕中心旋转 30 度
center = (w // 2, h // 2)
angle = 30
scale = 1.0
M = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(img, M, (w, h))
5.3 透视变换¶
透视变换(单应性)将四个源点映射到四个目标点,校正透视畸变。这在文档扫描、AR 标记检测和鸟瞰图生成中非常有用。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("robot_workspace.jpg")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w = img.shape[:2]
# 图像中文档/标记的四个角点(可能是歪斜的)
pts_src = np.float32([
[56, 65],
[368, 52],
[28, 387],
[389, 390]
])
# 期望输出矩形
output_w, output_h = 300, 400
pts_dst = np.float32([
[0, 0],
[output_w, 0],
[0, output_h],
[output_w, output_h]
])
# 计算透视矩阵
M = cv2.getPerspectiveTransform(pts_src, pts_dst)
# 应用透视变换
warped = cv2.warpPerspective(img_rgb, M, (output_w, output_h))
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(img_rgb)
axes[0].set_title("原始(有畸变)")
# 绘制源四边形
src_quad = np.vstack([pts_src, pts_src[0]])
axes[0].plot(src_quad[:, 0], src_quad[:, 1], 'r-', linewidth=2)
for pt in pts_src:
axes[0].plot(pt[0], pt[1], 'ro')
axes[1].imshow(warped)
axes[1].set_title("透视校正后")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
6. 机器人视觉实用流水线¶
在实际机器人系统中,你需要将多个预处理步骤组合成一个流水线。下面是一个完整的示例,检测彩色物体并计算其中心位置。
流水线步骤¶
完整流水线代码¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def preprocess_frame(frame):
"""应用去噪和增强。"""
# 使用双边滤波去噪(保留边缘)
denoised = cv2.bilateralFilter(frame, d=9, sigmaColor=75, sigmaSpace=75)
return denoised
def create_color_mask(hsv_frame, lower, upper):
"""为目标颜色创建并清理二值掩码。"""
mask = cv2.inRange(hsv_frame, lower, upper)
# 形态学清理
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
return mask
def detect_object(frame, mask):
"""查找最大轮廓并计算其质心。"""
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None, None, frame
# 按面积查找最大轮廓
largest = max(contours, key=cv2.contourArea)
# 跳过微小轮廓(噪声)
if cv2.contourArea(largest) < 500:
return None, None, frame
# 计算边界框和质心
x, y, w, h = cv2.boundingRect(largest)
M = cv2.moments(largest)
if M["m00"] > 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
else:
cx, cy = x + w // 2, y + h // 2
# 绘制结果
output = frame.copy()
cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.circle(output, (cx, cy), 5, (0, 0, 255), -1)
cv2.putText(output, f"({cx}, {cy})", (cx + 10, cy),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
return (cx, cy), (x, y, w, h), output
def run_pipeline(image_path):
"""完整的预处理和检测流水线。"""
# --- 步骤 1:采集 / 加载 ---
frame = cv2.imread(image_path)
if frame is None:
raise FileNotFoundError(f"无法加载图像: {image_path}")
# --- 步骤 2:去噪 ---
denoised = preprocess_frame(frame)
# --- 步骤 3:转换为 HSV ---
hsv = cv2.cvtColor(denoised, cv2.COLOR_BGR2HSV)
# --- 步骤 4:创建颜色掩码(检测蓝色物体) ---
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
mask = create_color_mask(hsv, lower_blue, upper_blue)
# --- 步骤 5:检测物体 ---
centroid, bbox, output = detect_object(denoised, mask)
if centroid:
print(f"检测到物体,质心: {centroid}, 边界框: {bbox}")
else:
print("未检测到物体。")
# --- 步骤 6:可视化 ---
fig, axes = plt.subplots(1, 4, figsize=(18, 4))
axes[0].imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
axes[0].set_title("1. 原始")
axes[1].imshow(cv2.cvtColor(denoised, cv2.COLOR_BGR2RGB))
axes[1].set_title("2. 去噪")
axes[2].imshow(mask, cmap="gray")
axes[2].set_title("3. 清理后掩码")
axes[3].imshow(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
axes[3].set_title("4. 检测结果")
for ax in axes:
ax.axis("off")
plt.suptitle("机器人视觉预处理流水线", fontsize=14)
plt.tight_layout()
plt.show()
return centroid, bbox
# --- 运行流水线 ---
if __name__ == "__main__":
centroid, bbox = run_pipeline("robot_workspace.jpg")
实时摄像头流水线(用于实际机器人)¶
import cv2
import numpy as np
def live_pipeline(camera_index=0):
"""在实时摄像头画面中运行预处理流水线。"""
cap = cv2.VideoCapture(camera_index)
if not cap.isOpened():
print("错误:无法打开摄像头。")
return
# 定义颜色范围(根据目标物体调整)
lower = np.array([100, 50, 50])
upper = np.array([130, 255, 255])
print("按 'q' 退出。")
while True:
ret, frame = cap.read()
if not ret:
break
# 流水线
denoised = cv2.bilateralFilter(frame, 9, 75, 75)
hsv = cv2.cvtColor(denoised, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) > 500:
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 显示结果
cv2.imshow("Frame", frame)
cv2.imshow("Mask", mask)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
live_pipeline()
7. 练习¶
练习 1:色彩空间探索(简单)¶
加载一张包含至少两个不同颜色物体的图像。转换到 HSV 并使用滑条分离每个物体。报告你找到的 HSV 范围。
练习 2:噪声去除挑战(中等)¶
向干净图像添加高斯噪声(σ=25)和椒盐噪声(5%)。应用高斯、中值和双边滤波器。使用 PSNR(峰值信噪比)比较结果:
import numpy as np
def psnr(original, processed):
mse = np.mean((original.astype(float) - processed.astype(float)) ** 2)
if mse == 0:
return float('inf')
return 10 * np.log10(255.0 ** 2 / mse)
哪种滤波器对每种噪声类型效果最好?为什么?
练习 3:车道线检测器(困难)¶
使用道路/赛道的俯视摄像头画面:
- 转换到 HSV 并掩蔽车道线标记(通常是白色或黄色)
- 用形态学操作清理掩码
- 应用透视变换获得鸟瞰图
- 使用
cv2.HoughLinesP检测车道线
练习 4:文档扫描器(困难)¶
编写一个函数,实现:
- 检测图像中最大的四边形轮廓
- 应用透视变换生成平坦的矩形输出
- 应用 CLAHE 增强可读性
练习 5:预处理调优(中等)¶
给定一张在光照不足条件下拍摄的图像(由助教提供),设计一个预处理流水线以最大化目标物体的可见性。记录每个步骤并解释你的选择。
参考资料¶
- OpenCV 文档 — docs.opencv.org
- OpenCV-Python 教程 — opencv-python-tutroals.readthedocs.io
- Szeliski, R. (2022). Computer Vision: Algorithms and Applications, 2nd ed. Springer. 第 3 章:图像处理。
- Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing, 4th ed. Pearson.
- Bradski, G. (2000). The OpenCV Library. Dr. Dobb's Journal of Software Tools.
本教程是港中深机器人课程的一部分。