Python 机器视觉入门:第6-10章完整学习指南

欢迎继续Python机器视觉的学习之旅!在前5章中,我们已经掌握了Python基础、NumPy数组操作和Matplotlib可视化技能。现在,让我们正式进入OpenCV的世界,学习图像处理的核心技术。本章内容将带你从零开始掌握OpenCV的基础操作,为后续项目打下坚实基础。


第6章:OpenCV安装与图像基础操作

6.1 OpenCV的安装与环境配置

重点:OpenCV-Python是OpenCV的Python接口,安装非常简单。

# 使用pip安装OpenCV核心库
pip install opencv-python

# 如果需要扩展模块(如特征提取等),可以安装
pip install opencv-contrib-python

安装后验证:

import cv2
print(cv2.__version__)  # 输出OpenCV版本号,例如 4.8.0

6.2 图像的读取、显示与保存

这是OpenCV最基础的操作,必须熟练掌握。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 1. 读取图像
# cv2.imread(文件名, 读取方式)
# 读取方式:cv2.IMREAD_COLOR(1) 彩色图(默认),cv2.IMREAD_GRAYSCALE(0) 灰度图,cv2.IMREAD_UNCHANGED(-1) 包含Alpha通道
img_color = cv2.imread('test.jpg', cv2.IMREAD_COLOR)
img_gray = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)

# 2. 检查图像是否读取成功
if img_color is None:
    print("图像读取失败,请检查文件路径")
    exit()

# 3. 显示图像
cv2.imshow('Color Image', img_color)  # 窗口名称为'Color Image'
cv2.imshow('Gray Image', img_gray)

# 等待按键,参数为等待毫秒数,0表示无限等待
key = cv2.waitKey(0)
if key == ord('s'):  # 如果按下's'键,保存图像
    cv2.imwrite('saved_image.jpg', img_color)

# 关闭所有窗口
cv2.destroyAllWindows()

# 4. 使用matplotlib显示(注意颜色问题)
# OpenCV读取的图像是BGR格式,matplotlib需要RGB格式
img_rgb = cv2.cvtColor(img_color, cv2.COLOR_BGR2RGB)
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.title('RGB Image')
plt.subplot(1, 2, 2)
plt.imshow(img_gray, cmap='gray')
plt.title('Gray Image')
plt.show()

重点

  • cv2.imread() 读取图像,返回NumPy数组
  • cv2.imshow() 显示图像,cv2.waitKey() 等待键盘输入,cv2.destroyAllWindows() 关闭窗口
  • OpenCV默认使用BGR颜色格式,而matplotlib使用RGB,显示彩色图像时需要转换

6.3 图像的基本属性

# 获取图像属性
print(f"图像形状: {img_color.shape}")      # (高度, 宽度, 通道数)
print(f"图像大小: {img_color.size}")        # 总像素数 = 高 * 宽 * 通道数
print(f"数据类型: {img_color.dtype}")       # uint8 (0-255)
print(f"宽度: {img_color.shape[1]}")        # 列数
print(f"高度: {img_color.shape[0]}")        # 行数
print(f"通道数: {img_color.shape[2]}")      # 3(彩色)

# 灰度图属性
print(f"灰度图形状: {img_gray.shape}")      # (高度, 宽度) - 单通道
print(f"灰度图通道数: {img_gray.ndim}")     # 2(二维数组)

重点:图像在OpenCV中就是NumPy数组,形状为 (height, width, channels),数据类型通常是 uint8(0-255)。

6.4 像素访问与修改

# 访问单个像素
img = cv2.imread('test.jpg')
px = img[100, 200]  # 第100行,第200列的像素,返回[B,G,R]数组
print(f"坐标(100,200)的像素值: {px}")  # 例如 [128 64 255]

# 访问单个通道值
blue_value = img[100, 200, 0]  # 通道0是蓝色
green_value = img[100, 200, 1]  # 通道1是绿色
red_value = img[100, 200, 2]    # 通道2是红色

# 修改像素
img[100, 200] = [255, 255, 255]  # 设置为白色

# 使用NumPy操作更高效
roi = img[50:150, 100:200]       # 获取感兴趣区域(50-150行,100-200列)
img[200:300, 300:400] = [0, 0, 255]  # 将区域设置为红色

重点:像素坐标顺序是 [行, 列],通道顺序是 [B, G, R]

6.5 色彩空间转换

# 读取图像
img = cv2.imread('test.jpg')

# BGR转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# BGR转RGB(用于matplotlib显示)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# BGR转HSV(色调、饱和度、明度)——常用于颜色分割
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# BGR转LAB
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

# 显示不同色彩空间
cv2.imshow('HSV Image', hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()

重点cv2.cvtColor() 是色彩空间转换函数,HSV空间对颜色分割非常有用,因为它将颜色信息(Hue)与亮度(Value)分离。

6.6 图像ROI(感兴趣区域)操作

# 选取ROI
img = cv2.imread('test.jpg')
roi = img[100:300, 200:400]  # 选取行100-300,列200-400的区域

# 复制ROI到另一个位置
img[50:250, 400:600] = roi  # 将ROI复制到新位置

# 显示ROI
cv2.imshow('ROI', roi)
cv2.waitKey(0)
cv2.destroyAllWindows()

重点:ROI操作通过NumPy切片实现,非常高效,常用于目标提取和复制。

6.7 通道拆分与合并

# 拆分通道
b, g, r = cv2.split(img)  # 返回三个单通道图像

# 或者使用NumPy索引
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]

# 合并通道
merged = cv2.merge([b, g, r])

# 创建单通道图像(例如只保留红色通道)
red_img = np.zeros_like(img)  # 创建全黑图像
red_img[:, :, 2] = r          # 只填充红色通道

# 显示各通道
cv2.imshow('Blue Channel', b)
cv2.imshow('Green Channel', g)
cv2.imshow('Red Channel', r)
cv2.waitKey(0)
cv2.destroyAllWindows()

重点cv2.split()cv2.merge() 用于通道操作,但NumPy索引通常更快。

6.8 综合案例:图像基础操作实战

import cv2
import numpy as np

# 创建一个简单的图像(300x300的彩色图)
img = np.zeros((300, 300, 3), dtype=np.uint8)

# 绘制一些基本形状
# 画一条线(从(50,50)到(250,250),红色,线宽5)
cv2.line(img, (50, 50), (250, 250), (0, 0, 255), 5)

# 画一个矩形(左上角(50,100),右下角(200,250),绿色,线宽3)
cv2.rectangle(img, (50, 100), (200, 250), (0, 255, 0), 3)

# 画一个圆(中心(150,150),半径50,蓝色,线宽-1表示填充)
cv2.circle(img, (150, 150), 50, (255, 0, 0), -1)

# 添加文字
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'OpenCV', (50, 280), font, 1, (255, 255, 255), 2)

# 显示图像
cv2.imshow('Drawing', img)

# 获取鼠标点击位置的像素值
def mouse_callback(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        print(f"坐标({x}, {y})的像素值: {img[y, x]}")  # 注意:坐标是(x,y),但NumPy索引是[y,x]

cv2.setMouseCallback('Drawing', mouse_callback)

cv2.waitKey(0)
cv2.destroyAllWindows()

第7章:图像预处理(滤波与增强)

图像预处理是机器视觉中至关重要的一步,目的是去除噪声、增强特征,为后续处理做准备。

7.1 图像的几何变换(缩放、旋转、翻转)

import cv2
import numpy as np

img = cv2.imread('test.jpg')

# 1. 图像缩放
# 指定目标大小
resized = cv2.resize(img, (300, 300))
# 按比例缩放(fx,fy为缩放因子)
resized_ratio = cv2.resize(img, None, fx=0.5, fy=0.5)

# 2. 图像旋转
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
# 获取旋转矩阵(中心点,角度,缩放因子)
M = cv2.getRotationMatrix2D(center, 45, 1.0)  # 旋转45度
rotated = cv2.warpAffine(img, M, (w, h))

# 3. 图像翻转
flip_h = cv2.flip(img, 1)   # 水平翻转(左右)
flip_v = cv2.flip(img, 0)   # 垂直翻转(上下)
flip_both = cv2.flip(img, -1) # 同时翻转

# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Resized', resized)
cv2.imshow('Rotated', rotated)
cv2.imshow('Flip Horizontal', flip_h)
cv2.waitKey(0)
cv2.destroyAllWindows()

重点cv2.resize()cv2.getRotationMatrix2D() + cv2.warpAffine()cv2.flip() 是常用的几何变换函数。

7.2 图像的算术运算

# 创建两个图像
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')

# 确保两张图大小相同
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))

# 图像加法(直接加法可能导致溢出,但NumPy会取模)
add_direct = img1 + img2  # 可能导致数值环绕(255+1=0)

# OpenCV加法(饱和操作,255+1=255)
add_cv = cv2.add(img1, img2)

# 图像加权混合(α*img1 + β*img2 + γ)
blended = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)

# 显示结果
cv2.imshow('Add (NumPy)', add_direct)
cv2.imshow('Add (OpenCV)', add_cv)
cv2.imshow('Blended', blended)
cv2.waitKey(0)
cv2.destroyAllWindows()

重点cv2.add() 是饱和操作,而NumPy加法是模运算;cv2.addWeighted() 常用于图像融合。

7.3 图像阈值处理(二值化)

阈值处理将灰度图像转换为二值图像,是图像分割的基础。

# 读取灰度图像
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)

# 1. 简单阈值
# cv2.threshold(图像, 阈值, 最大值, 阈值类型)
# 阈值类型:THRESH_BINARY, THRESH_BINARY_INV, THRESH_TRUNC, THRESH_TOZERO, THRESH_TOZERO_INV
ret, thresh_binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh_binary_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)

# 2. 自适应阈值(根据局部区域计算阈值,适合光照不均的图像)
# cv2.adaptiveThreshold(图像, 最大值, 自适应方法, 阈值类型, 邻域大小, 常数C)
# 自适应方法:ADAPTIVE_THRESH_MEAN_C(邻域均值减去C),ADAPTIVE_THRESH_GAUSSIAN_C(邻域加权均值减去C)
adaptive_mean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, 
                                       cv2.THRESH_BINARY, 11, 2)
adaptive_gaussian = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                            cv2.THRESH_BINARY, 11, 2)

# 3. Otsu阈值法(自动计算最佳阈值,适合双峰直方图图像)
ret_otsu, thresh_otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu自动计算阈值: {ret_otsu}")

# 显示结果
titles = ['Original', 'Binary', 'Binary Inv', 'Adaptive Mean', 'Adaptive Gaussian', 'Otsu']
images = [img, thresh_binary, thresh_binary_inv, adaptive_mean, adaptive_gaussian, thresh_otsu]

for i in range(6):
    plt.subplot(2, 3, i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

重点

  • 简单阈值需要手动设定阈值
  • 自适应阈值适合光照不均的图像,根据局部区域计算阈值
  • Otsu算法自动计算最优阈值,适用于直方图呈双峰分布的图像

7.4 图像滤波(平滑与去噪)

滤波是图像预处理的核心,用于去除噪声、平滑图像 。

7.4.1 均值滤波
# 均值滤波:用邻域像素的平均值代替中心像素
# cv2.blur(图像, 卷积核大小)
blur_3x3 = cv2.blur(img, (3, 3))  # 3x3卷积核
blur_5x5 = cv2.blur(img, (5, 5))  # 5x5卷积核(更大核,更模糊)
7.4.2 高斯滤波
# 高斯滤波:用加权平均值代替,中心像素权重最大
# cv2.GaussianBlur(图像, 卷积核大小, 标准差)
gaussian_3x3 = cv2.GaussianBlur(img, (3, 3), 1)
gaussian_5x5 = cv2.GaussianBlur(img, (5, 5), 1.5)
7.4.3 中值滤波
# 中值滤波:用邻域像素的中值代替,对椒盐噪声特别有效
# cv2.medianBlur(图像, 卷积核大小) - 核大小必须是奇数
median_3 = cv2.medianBlur(img, 3)  # 3x3邻域
median_5 = cv2.medianBlur(img, 5)  # 5x5邻域
7.4.4 双边滤波
# 双边滤波:保留边缘的同时去噪
# cv2.bilateralFilter(图像, 邻域直径, 颜色标准差, 空间标准差)
# 颜色标准差越大,颜色差异大的像素越容易被考虑;空间标准差越大,越远的像素越容易被考虑
bilateral = cv2.bilateralFilter(img, 9, 75, 75)
7.4.5 滤波效果对比
# 完整示例:添加噪声并比较滤波效果
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像并添加噪声
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)

# 添加椒盐噪声
def add_salt_pepper_noise(image, prob):
    output = image.copy()
    h, w = image.shape
    num_noise = int(prob * h * w)
    
    # 随机添加白点(盐噪声)
    coords = [np.random.randint(0, i-1, num_noise) for i in [h, w]]
    output[coords[0], coords[1]] = 255
    
    # 随机添加黑点(椒噪声)
    coords = [np.random.randint(0, i-1, num_noise) for i in [h, w]]
    output[coords[0], coords[1]] = 0
    
    return output

# 添加高斯噪声
def add_gaussian_noise(image, mean=0, sigma=25):
    noise = np.random.normal(mean, sigma, image.shape)
    output = image + noise
    return np.clip(output, 0, 255).astype(np.uint8)

# 生成带噪声的图像
img_sp = add_salt_pepper_noise(img, 0.02)  # 2%椒盐噪声
img_gaussian = add_gaussian_noise(img, 0, 25)  # 高斯噪声

# 应用各种滤波
# 对椒盐噪声图像滤波
mean_sp = cv2.blur(img_sp, (3, 3))
gaussian_sp = cv2.GaussianBlur(img_sp, (3, 3), 1)
median_sp = cv2.medianBlur(img_sp, 3)

# 对高斯噪声图像滤波
mean_gauss = cv2.blur(img_gaussian, (3, 3))
gaussian_gauss = cv2.GaussianBlur(img_gaussian, (3, 3), 1)
bilateral_gauss = cv2.bilateralFilter(img_gaussian, 9, 75, 75)

# 显示结果
plt.figure(figsize=(15, 10))

# 椒盐噪声滤波效果
plt.subplot(2, 4, 1)
plt.imshow(img_sp, cmap='gray')
plt.title('Salt & Pepper Noise')
plt.axis('off')

plt.subplot(2, 4, 2)
plt.imshow(mean_sp, cmap='gray')
plt.title('Mean Filter')
plt.axis('off')

plt.subplot(2, 4, 3)
plt.imshow(gaussian_sp, cmap='gray')
plt.title('Gaussian Filter')
plt.axis('off')

plt.subplot(2, 4, 4)
plt.imshow(median_sp, cmap='gray')
plt.title('Median Filter')
plt.axis('off')

# 高斯噪声滤波效果
plt.subplot(2, 4, 5)
plt.imshow(img_gaussian, cmap='gray')
plt.title('Gaussian Noise')
plt.axis('off')

plt.subplot(2, 4, 6)
plt.imshow(mean_gauss, cmap='gray')
plt.title('Mean Filter')
plt.axis('off')

plt.subplot(2, 4, 7)
plt.imshow(gaussian_gauss, cmap='gray')
plt.title('Gaussian Filter')
plt.axis('off')

plt.subplot(2, 4, 8)
plt.imshow(bilateral_gauss, cmap='gray')
plt.title('Bilateral Filter')
plt.axis('off')

plt.tight_layout()
plt.show()

重点

  • 均值滤波:简单但边缘模糊
  • 高斯滤波:加权平均,更自然,但也会模糊边缘
  • 中值滤波:对椒盐噪声效果最好,能保留边缘
  • 双边滤波:去噪同时保留边缘,但计算量大
  • 噪声类型识别很重要:椒盐噪声用中值滤波,高斯噪声用高斯滤波或双边滤波

7.5 形态学操作

形态学操作是基于图像形状的一系列操作,常用于二值图像处理 。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 创建一个简单的二值图像
img = np.zeros((300, 300), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (150, 150), 255, -1)  # 白色矩形
cv2.circle(img, (200, 200), 50, 255, -1)  # 白色圆形

# 添加一些噪声点
img[20, 20] = 255
img[250, 250] = 255

# 定义结构元素(卷积核)
kernel = np.ones((5, 5), np.uint8)

# 1. 腐蚀:消除边界点,使白色区域缩小
erosion = cv2.erode(img, kernel, iterations=1)

# 2. 膨胀:扩大边界点,使白色区域扩大
dilation = cv2.dilate(img, kernel, iterations=1)

# 3. 开运算:先腐蚀后膨胀,用于去除小噪点
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

# 4. 闭运算:先膨胀后腐蚀,用于填充小孔
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

# 5. 形态学梯度:膨胀减腐蚀,得到轮廓
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

# 6. 顶帽运算:原图减开运算,提取亮区域中的小物体
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

# 7. 黑帽运算:闭运算减原图,提取暗区域中的小物体
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

# 显示结果
titles = ['Original', 'Erosion', 'Dilation', 'Opening', 'Closing', 'Gradient', 'Tophat', 'Blackhat']
images = [img, erosion, dilation, opening, closing, gradient, tophat, blackhat]

plt.figure(figsize=(15, 10))
for i in range(8):
    plt.subplot(2, 4, i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.axis('off')
plt.tight_layout()
plt.show()

重点

  • 腐蚀:去除边界点,分离相连物体
  • 膨胀:连接相邻物体,填补空洞
  • 开运算:先腐蚀后膨胀,去除小噪点
  • 闭运算:先膨胀后腐蚀,填补小孔
  • 结构元素(kernel)的形状和大小影响操作效果,常用矩形、十字形、椭圆形

第8章:边缘检测与特征提取

边缘是图像中像素值发生剧烈变化的位置,边缘检测是图像分割和目标识别的基础。

8.1 图像梯度(Sobel、Scharr、Laplacian)

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)

# 1. Sobel算子(一阶导数)
# cv2.Sobel(图像, 数据类型, dx, dy, ksize)
# dx=1, dy=0 计算x方向梯度(垂直边缘)
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
# dx=0, dy=1 计算y方向梯度(水平边缘)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)

# 转换为uint8类型(取绝对值)
sobel_x_abs = cv2.convertScaleAbs(sobel_x)
sobel_y_abs = cv2.convertScaleAbs(sobel_y)

# 合并梯度
sobel_combined = cv2.addWeighted(sobel_x_abs, 0.5, sobel_y_abs, 0.5, 0)

# 2. Scharr算子(更精确的梯度计算,核大小为3x3的优化版本)
scharr_x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharr_x_abs = cv2.convertScaleAbs(scharr_x)
scharr_y_abs = cv2.convertScaleAbs(scharr_y)
scharr_combined = cv2.addWeighted(scharr_x_abs, 0.5, scharr_y_abs, 0.5, 0)

# 3. Laplacian算子(二阶导数,对噪声敏感)
laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian_abs = cv2.convertScaleAbs(laplacian)

# 显示结果
titles = ['Original', 'Sobel X', 'Sobel Y', 'Sobel Combined', 'Scharr Combined', 'Laplacian']
images = [img, sobel_x_abs, sobel_y_abs, sobel_combined, scharr_combined, laplacian_abs]

plt.figure(figsize=(15, 8))
for i in range(6):
    plt.subplot(2, 3, i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.axis('off')
plt.tight_layout()
plt.show()

重点

  • Sobel算子:计算一阶导数,分别检测水平和垂直边缘
  • Scharr算子:Sobel的改进版,精度更高
  • Laplacian算子:计算二阶导数,对噪声敏感,通常先平滑再使用

8.2 Canny边缘检测(最常用的边缘检测算法)

Canny算法是多阶段边缘检测器,效果好、抗噪能力强 。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)

# 1. 基本Canny边缘检测
# cv2.Canny(图像, 低阈值, 高阈值)
# 低阈值:低于此值的点不是边缘;高阈值:高于此值的点是强边缘;介于之间的点如果与强边缘相连则视为边缘
edges1 = cv2.Canny(img, 50, 150)

# 2. 不同阈值的效果对比
edges2 = cv2.Canny(img, 100, 200)  # 更高阈值,检测更少边缘
edges3 = cv2.Canny(img, 30, 80)     # 更低阈值,检测更多边缘(可能包含噪声)

# 3. 自动计算阈值(使用中位数)
median = np.median(img)
low = int(max(0, 0.7 * median))
high = int(min(255, 1.3 * median))
edges_auto = cv2.Canny(img, low, high)

# 4. 结合高斯滤波(先平滑再检测)
blurred = cv2.GaussianBlur(img, (5, 5), 1.5)
edges_blur = cv2.Canny(blurred, 50, 150)

# 显示结果
titles = ['Original', 'Canny (50,150)', 'Canny (100,200)', 'Canny (30,80)', 'Auto Threshold', 'With Blur']
images = [img, edges1, edges2, edges3, edges_auto, edges_blur]

plt.figure(figsize=(15, 8))
for i in range(6):
    plt.subplot(2, 3, i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.axis('off')
plt.tight_layout()
plt.show()

重点

  • Canny算法步骤:高斯滤波去噪 → 计算梯度幅值和方向 → 非极大值抑制 → 双阈值检测
  • 阈值选择:高阈值决定强边缘,低阈值控制边缘连续性
  • 先滤波后检测能减少噪声引起的假边缘

8.3 霍夫变换(直线和圆检测)

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像并转为灰度
img = cv2.imread('shapes.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 先用Canny检测边缘
edges = cv2.Canny(gray, 50, 150, apertureSize=3)

# 1. 霍夫直线检测
# cv2.HoughLines(边缘图像, 距离分辨率rho, 角度分辨率theta, 阈值)
lines = cv2.HoughLines(edges, 1, np.pi/180, 150)

# 在原图上绘制检测到的直线
img_lines = img.copy()
if lines is not None:
    for line in lines:
        rho, theta = line[0]
        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))
        cv2.line(img_lines, (x1, y1), (x2, y2), (0, 0, 255), 2)

# 2. 概率霍夫直线检测(更常用,直接得到线段端点)
lines_p = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=10)

img_lines_p = img.copy()
if lines_p is not None:
    for line in lines_p:
        x1, y1, x2, y2 = line[0]
        cv2.line(img_lines_p, (x1, y1), (x2, y2), (0, 255, 0), 2)

# 3. 霍夫圆检测
# cv2.HoughCircles(图像, 检测方法, 分辨率比, 最小圆心距离, param1, param2, minRadius, maxRadius)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
                           param1=50, param2=30, minRadius=0, maxRadius=0)

img_circles = img.copy()
if circles is not None:
    circles = np.round(circles[0, :]).astype("int")
    for (x, y, r) in circles:
        # 画圆
        cv2.circle(img_circles, (x, y), r, (0, 255, 0), 2)
        # 画圆心
        cv2.circle(img_circles, (x, y), 2, (0, 0, 255), 3)

# 显示结果
plt.figure(figsize=(15, 10))
plt.subplot(2, 2, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Original')
plt.axis('off')

plt.subplot(2, 2, 2)
plt.imshow(cv2.cvtColor(img_lines, cv2.COLOR_BGR2RGB))
plt.title('Hough Lines')
plt.axis('off')

plt.subplot(2, 2, 3)
plt.imshow(cv2.cvtColor(img_lines_p, cv2.COLOR_BGR2RGB))
plt.title('Probabilistic Hough Lines')
plt.axis('off')

plt.subplot(2, 2, 4)
plt.imshow(cv2.cvtColor(img_circles, cv2.COLOR_BGR2RGB))
plt.title('Hough Circles')
plt.axis('off')

plt.tight_layout()
plt.show()

重点

  • 霍夫直线检测:将图像空间中的直线映射到参数空间(极坐标)
  • 概率霍夫变换:更高效,直接返回线段端点
  • 霍夫圆检测:需要调节param1(Canny高阈值)和param2(圆心检测阈值)

第9章:图像分割与轮廓分析

图像分割是将图像划分为有意义的区域,轮廓分析则是提取和处理这些区域的边界。

9.1 基于阈值的分割(OTSU、Triangle)

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 1. OTSU阈值分割(自动阈值)
ret_otsu, thresh_otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"OTSU自动阈值: {ret_otsu}")

# 2. Triangle阈值分割(适用于单峰直方图)
ret_triangle, thresh_triangle = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
print(f"Triangle自动阈值: {ret_triangle}")

# 3. 自适应阈值(局部阈值)
thresh_adaptive = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                        cv2.THRESH_BINARY, 11, 2)

# 显示结果
titles = ['Original', 'OTSU', 'Triangle', 'Adaptive']
images = [gray, thresh_otsu, thresh_triangle, thresh_adaptive]

plt.figure(figsize=(12, 8))
for i in range(4):
    plt.subplot(2, 2, i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.axis('off')
plt.tight_layout()
plt.show()

重点

  • OTSU:假设图像直方图呈双峰分布,自动计算峰谷阈值
  • Triangle:适用于单峰直方图(如背景单一,前景较小)
  • 自适应阈值:处理光照不均的图像效果好

9.2 轮廓查找与绘制

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像并转为灰度
img = cv2.imread('shapes.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 阈值分割(便于找到轮廓)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 查找轮廓
# cv2.findContours(二值图像, 轮廓检索模式, 轮廓近似方法)
# 检索模式:RETR_EXTERNAL(只检测最外层轮廓)、RETR_LIST(检测所有轮廓)、RETR_TREE(检测所有轮廓并建立等级关系)
# 近似方法:CHAIN_APPROX_SIMPLE(压缩水平、垂直、对角线方向,只保留端点),CHAIN_APPROX_NONE(保留所有点)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

print(f"找到 {len(contours)} 个轮廓")

# 在原图上绘制所有轮廓
img_contours = img.copy()
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 2)  # -1表示绘制所有轮廓

# 绘制特定轮廓(例如第0个)
img_contour0 = img.copy()
cv2.drawContours(img_contour0, contours, 0, (0, 0, 255), 3)

# 显示结果
plt.figure(figsize=(15, 10))
plt.subplot(2, 2, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Original')
plt.axis('off')

plt.subplot(2, 2, 2)
plt.imshow(thresh, cmap='gray')
plt.title('Threshold')
plt.axis('off')

plt.subplot(2, 2, 3)
plt.imshow(cv2.cvtColor(img_contours, cv2.COLOR_BGR2RGB))
plt.title('All Contours')
plt.axis('off')

plt.subplot(2, 2, 4)
plt.imshow(cv2.cvtColor(img_contour0, cv2.COLOR_BGR2RGB))
plt.title('First Contour')
plt.axis('off')

plt.tight_layout()
plt.show()

重点

  • findContours() 输入必须是二值图像
  • 轮廓是点集(NumPy数组),可用 drawContours() 绘制
  • 轮廓检索模式选择影响轮廓的组织方式

9.3 轮廓特征(面积、周长、外接矩形)

# 遍历每个轮廓,计算特征
for i, contour in enumerate(contours):
    # 1. 轮廓面积
    area = cv2.contourArea(contour)
    
    # 2. 轮廓周长(弧长)
    perimeter = cv2.arcLength(contour, True)  # True表示闭合轮廓
    
    # 3. 外接矩形(正矩形)
    x, y, w, h = cv2.boundingRect(contour)
    
    # 4. 最小外接矩形(带旋转角度)
    rect = cv2.minAreaRect(contour)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    
    # 5. 外接圆
    (cx, cy), radius = cv2.minEnclosingCircle(contour)
    center = (int(cx), int(cy))
    radius = int(radius)
    
    # 6. 轮廓近似(减少顶点数量)
    epsilon = 0.02 * perimeter
    approx = cv2.approxPolyDP(contour, epsilon, True)
    
    # 7. 凸包
    hull = cv2.convexHull(contour)
    
    # 8. 判断轮廓是否是凸的
    is_convex = cv2.isContourConvex(contour)
    
    # 9. 计算矩(用于计算重心、方向等)
    moments = cv2.moments(contour)
    if moments['m00'] != 0:
        centroid_x = int(moments['m10'] / moments['m00'])
        centroid_y = int(moments['m01'] / moments['m00'])
    else:
        centroid_x, centroid_y = 0, 0
    
    # 只打印面积较大的轮廓
    if area > 100:
        print(f"轮廓 {i}: 面积={area:.2f}, 周长={perimeter:.2f}, 顶点数={len(contour)}")
        print(f"  外接矩形: x={x}, y={y}, w={w}, h={h}")
        print(f"  重心: ({centroid_x}, {centroid_y})")
        print(f"  凸性: {is_convex}")
        print("---")

重点:轮廓特征是实现形状识别的基础,如区分圆形、矩形等 。

9.4 形状识别实战:检测几何形状

import cv2
import numpy as np
import matplotlib.pyplot as plt

def detect_shape(contour):
    """根据轮廓顶点数判断形状"""
    # 轮廓近似
    perimeter = cv2.arcLength(contour, True)
    epsilon = 0.04 * perimeter
    approx = cv2.approxPolyDP(contour, epsilon, True)
    
    vertices = len(approx)
    
    if vertices == 3:
        return "Triangle"
    elif vertices == 4:
        # 判断是否为正方形
        x, y, w, h = cv2.boundingRect(approx)
        aspect_ratio = w / h
        if 0.95 <= aspect_ratio <= 1.05:
            return "Square"
        else:
            return "Rectangle"
    elif vertices == 5:
        return "Pentagon"
    elif vertices == 6:
        return "Hexagon"
    elif vertices > 6:
        # 判断是否为圆
        area = cv2.contourArea(contour)
        (x, y), radius = cv2.minEnclosingCircle(contour)
        circle_area = np.pi * radius * radius
        circularity = area / circle_area
        
        if circularity > 0.85:
            return "Circle"
        else:
            return "Polygon"
    else:
        return "Unknown"

# 读取图像
img = cv2.imread('shapes.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 阈值处理
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 查找轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 在原图上绘制并标注形状
img_result = img.copy()
for i, contour in enumerate(contours):
    # 忽略小轮廓
    area = cv2.contourArea(contour)
    if area < 100:
        continue
    
    # 识别形状
    shape_name = detect_shape(contour)
    
    # 绘制轮廓
    cv2.drawContours(img_result, [contour], -1, (0, 255, 0), 2)
    
    # 计算重心用于放置文字
    M = cv2.moments(contour)
    if M['m00'] != 0:
        cx = int(M['m10'] / M['m00'])
        cy = int(M['m01'] / M['m00'])
    else:
        cx, cy = 0, 0
    
    # 标注形状名称
    cv2.putText(img_result, shape_name, (cx-50, cy), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)

# 显示结果
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Original')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.title('Shape Detection')
plt.axis('off')
plt.tight_layout()
plt.show()

重点

  • 通过 approxPolyDP() 轮廓近似,减少顶点数量
  • 根据顶点数判断基本形状
  • 圆形判断需要计算圆形度(面积/外接圆面积)

第10章:模板匹配与特征匹配

模板匹配是在大图中寻找与模板图像最相似的区域,常用于目标定位 。

10.1 模板匹配基础

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取源图像和模板图像
img = cv2.imread('source.jpg')  # 大图
template = cv2.imread('template.jpg')  # 小图(模板)

# 转为灰度
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)

# 获取模板尺寸
h, w = template_gray.shape

# 模板匹配
# cv2.matchTemplate(源图像, 模板, 匹配方法)
# 匹配方法:
# TM_SQDIFF:平方差匹配(值越小越匹配)
# TM_CCORR:相关匹配(值越大越匹配)
# TM_CCOEFF:相关系数匹配(值越大越匹配)
# 还有对应的归一化版本:TM_SQDIFF_NORMED, TM_CCORR_NORMED, TM_CCOEFF_NORMED
result = cv2.matchTemplate(img_gray, template_gray, cv2.TM_CCOEFF_NORMED)

# 找到最佳匹配位置
# cv2.minMaxLoc() 返回最小值、最大值、最小值位置、最大值位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

# 对于TM_CCOEFF_NORMED,最大值位置是最佳匹配
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)

# 在源图像上绘制矩形
img_result = img.copy()
cv2.rectangle(img_result, top_left, bottom_right, (0, 255, 0), 2)

# 显示匹配结果
print(f"匹配值: {max_val}")  # 越接近1匹配越好

plt.figure(figsize=(12, 6))
plt.subplot(1, 3, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Source Image')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(cv2.cvtColor(template, cv2.COLOR_BGR2RGB))
plt.title('Template')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.title('Matching Result')
plt.axis('off')

plt.tight_layout()
plt.show()

重点

  • matchTemplate() 返回一个结果矩阵,每个位置表示该区域的匹配度
  • minMaxLoc() 找到最佳匹配位置
  • 不同匹配方法的判断准则不同(最小值还是最大值)

10.2 多目标模板匹配

当图像中有多个相同目标时,需要设置阈值来匹配多个位置。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像和模板
img = cv2.imread('multi_targets.jpg')
template = cv2.imread('target.jpg')

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
h, w = template_gray.shape

# 模板匹配
result = cv2.matchTemplate(img_gray, template_gray, cv2.TM_CCOEFF_NORMED)

# 设置阈值
threshold = 0.8

# 找到所有匹配值大于阈值的位置
locations = np.where(result >= threshold)

# 绘制所有匹配的矩形
img_result = img.copy()
for pt in zip(*locations[::-1]):  # locations是(y,x),需要转为(x,y)
    cv2.rectangle(img_result, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 1)

# 显示结果
print(f"找到 {len(locations[0])} 个匹配")

plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Original')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.title(f'Multiple Matches (threshold={threshold})')
plt.axis('off')

plt.tight_layout()
plt.show()

重点:通过阈值筛选,可以找到多个匹配位置 。

10.3 模板匹配的局限性及改进

模板匹配对旋转和缩放敏感,改进方法包括:

import cv2
import numpy as np
import matplotlib.pyplot as plt

def template_match_rotation_scale(img, template, angles, scales):
    """多角度多尺度的模板匹配"""
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
    h, w = template_gray.shape
    
    best_val = -1
    best_loc = None
    best_angle = 0
    best_scale = 1
    best_matrix = None
    
    for scale in scales:
        # 缩放模板
        new_w = int(w * scale)
        new_h = int(h * scale)
        if new_w < 10 or new_h < 10:
            continue
        
        scaled_template = cv2.resize(template_gray, (new_w, new_h))
        
        for angle in angles:
            # 旋转模板
            center = (new_w // 2, new_h // 2)
            rot_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
            rotated_template = cv2.warpAffine(scaled_template, rot_matrix, (new_w, new_h))
            
            # 确保模板尺寸小于源图像
            if new_h > img_gray.shape[0] or new_w > img_gray.shape[1]:
                continue
            
            # 模板匹配
            result = cv2.matchTemplate(img_gray, rotated_template, cv2.TM_CCOEFF_NORMED)
            _, max_val, _, max_loc = cv2.minMaxLoc(result)
            
            if max_val > best_val:
                best_val = max_val
                best_loc = max_loc
                best_angle = angle
                best_scale = scale
                best_matrix = rot_matrix
    
    return best_val, best_loc, best_angle, best_scale, best_matrix

# 使用示例
img = cv2.imread('source.jpg')
template = cv2.imread('template.jpg')

# 测试多个角度和尺度
angles = [0, 45, 90, 135, 180, 225, 270, 315]
scales = [0.5, 0.75, 1.0, 1.25, 1.5]

best_val, best_loc, best_angle, best_scale, _ = template_match_rotation_scale(img, template, angles, scales)

print(f"最佳匹配值: {best_val}")
print(f"最佳角度: {best_angle}°")
print(f"最佳尺度: {best_scale}")
print(f"最佳位置: {best_loc}")

重点:实际工业应用中,常用特征点匹配(如SIFT、ORB)来解决旋转缩放问题,而不是多角度模板匹配(计算量大)。

10.4 特征点匹配(SIFT/ORB)

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img1 = cv2.imread('object.jpg')  # 模板图像
img2 = cv2.imread('scene.jpg')    # 场景图像

# 转为灰度
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 1. SIFT特征检测器(需要安装opencv-contrib-python)
# SIFT是尺度不变特征变换,对旋转和缩放具有不变性
sift = cv2.SIFT_create()

# 检测关键点和计算描述子
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)

# 2. ORB特征检测器(速度更快,适合实时应用)
orb = cv2.ORB_create()
kp1_orb, des1_orb = orb.detectAndCompute(gray1, None)
kp2_orb, des2_orb = orb.detectAndCompute(gray2, None)

# 3. 特征匹配
# 使用BFMatcher(暴力匹配)
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
matches = bf.match(des1, des2)

# 按距离排序(距离越小匹配越好)
matches = sorted(matches, key=lambda x: x.distance)

# 绘制前10个匹配
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

# 显示结果
plt.figure(figsize=(15, 8))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB))
plt.title('Template')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))
plt.title('Scene')
plt.axis('off')

plt.figure(figsize=(15, 8))
plt.imshow(cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB))
plt.title('SIFT Matches')
plt.axis('off')
plt.show()

重点

  • SIFT:对旋转、缩放、光照变化都有很好的鲁棒性,但计算量较大
  • ORB:速度很快,适合实时应用,但鲁棒性稍差
  • 特征点匹配是目标检测和识别的基础

10.5 综合案例:使用特征点匹配实现对象检测

import cv2
import numpy as np
import matplotlib.pyplot as plt

def find_homography_and_detect(img_template, img_scene):
    """使用特征点匹配和单应性矩阵找到场景中的模板对象"""
    
    # 初始化SIFT
    sift = cv2.SIFT_create()
    
    # 检测关键点和描述子
    kp1, des1 = sift.detectAndCompute(img_template, None)
    kp2, des2 = sift.detectAndCompute(img_scene, None)
    
    # FLANN匹配器参数
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    search_params = dict(checks=50)
    
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    matches = flann.knnMatch(des1, des2, k=2)
    
    # Lowe's ratio test 筛选好匹配
    good_matches = []
    for m, n in matches:
        if m.distance < 0.7 * n.distance:
            good_matches.append(m)
    
    print(f"找到 {len(good_matches)} 个好匹配")
    
    # 至少需要4个好匹配才能计算单应性矩阵
    if len(good_matches) >= 4:
        # 获取匹配点的坐标
        src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
        dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
        
        # 计算单应性矩阵
        H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
        
        # 获取模板图像的四个角点
        h, w = img_template.shape[:2]
        pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2)
        
        # 投影到场景图像中
        dst = cv2.perspectiveTransform(pts, H)
        
        # 绘制检测结果
        img_result = img_scene.copy()
        cv2.polylines(img_result, [np.int32(dst)], True, (0, 255, 0), 3)
        
        return img_result, good_matches, mask
    else:
        print("匹配点不足,无法检测对象")
        return img_scene, good_matches, None

# 使用示例
template = cv2.imread('template.jpg')
scene = cv2.imread('scene.jpg')

# 转为灰度
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
scene_gray = cv2.cvtColor(scene, cv2.COLOR_BGR2GRAY)

# 检测对象
img_detected, good_matches, mask = find_homography_and_detect(template_gray, scene_gray)

# 显示结果
plt.figure(figsize=(15, 8))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(template, cv2.COLOR_BGR2RGB))
plt.title('Template')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img_detected, cv2.COLOR_BGR2RGB))
plt.title('Detected Object')
plt.axis('off')
plt.show()

重点

  • 单应性矩阵(Homography)描述了两个平面之间的投影变换
  • 通过特征点匹配和单应性矩阵,可以在复杂场景中精确定位目标对象,即使目标有旋转、缩放和透视变化
  • 这是增强现实、物体跟踪的基础技术

学习建议与下一阶段预告

恭喜你完成了OpenCV核心基础(第6-10章)的学习!现在你已经掌握了:

  1. 图像基础操作:读写显示、像素访问、色彩空间
  2. 图像预处理:几何变换、阈值处理、滤波去噪、形态学操作
  3. 边缘检测:Sobel、Canny、霍夫变换
  4. 图像分割:阈值分割、轮廓查找与分析
  5. 目标定位:模板匹配、特征点匹配

实战建议

  • 找一些包含简单几何图形的图片,练习轮廓检测和形状识别
  • 尝试用模板匹配定位自己拍摄的照片中的特定物体
  • 结合之前学的知识,实现一个简单的"图像中找圆形"的程序

下一阶段(第11-13章)预告

  • 相机标定与三维视觉
  • 视频处理与实时采集
  • 深度学习与OpenCV DNN模块(YOLO目标检测)

如果你在学习过程中遇到任何问题,或者想要深入某个专题的实战案例,随时可以提问!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐