Python图像处理与计算机视觉:OpenCV实战指南
引言
在当今数字化时代,图像处理和计算机视觉技术已经渗透到我们生活的方方面面,从智能手机的人脸识别解锁,到自动驾驶汽车的路况感知,再到医疗影像辅助诊断系统。作为这一领域最流行的开源库之一,OpenCV (Open Source Computer Vision Library) 以其强大的功能、优秀的性能和跨平台特性,成为了图像处理和计算机视觉应用开发的首选工具。
本文将带领读者深入探索Python与OpenCV的结合使用,从基础概念入手,逐步掌握图像处理的核心技术,并通过实际案例展示如何应用这些技术解决现实问题。无论你是计算机视觉领域的新手,还是希望提升技能的专业人士,这篇指南都将为你提供宝贵的实践经验和技术洞见。
目录
-
OpenCV基础与环境搭建
- OpenCV简介
- 安装与配置
- 基本图像操作
-
图像处理基础
- 色彩空间转换
- 图像滤波与平滑
- 边缘检测
- 图像增强与修复
-
特征提取与描述
- 传统特征提取方法
- 颜色特征提取
- 纹理特征提取
- 特征匹配与应用
-
图像分割技术
- 基于阈值的分割
- 基于边缘的分割
- 基于区域的分割
- 基于深度学习的分割
-
目标检测与识别
- 传统机器学习方法
- 深度学习方法
- 实时目标检测应用
-
实战项目与应用案例
- 文档扫描与矫正
- 人脸检测与识别
- 车牌识别系统
- 医学图像分析
1. OpenCV基础与环境搭建
1.1 OpenCV简介
OpenCV (Open Source Computer Vision Library) 是一个开源的计算机视觉和机器学习软件库,最初由英特尔开发,现在由非营利组织OpenCV.org维护。自1999年首次发布以来,OpenCV已经发展成为计算机视觉领域最广泛使用的库之一,拥有超过2500个优化算法,涵盖了从基础图像处理到高级机器学习的各个方面。
OpenCV的主要优势包括:
- 跨平台支持:可在Windows、Linux、macOS、Android和iOS等多种平台上运行
- 多语言接口:支持C++、Python、Java等多种编程语言
- 高效性能:许多算法经过优化,可以实现实时处理
- 活跃的社区:拥有庞大的用户群体和丰富的学习资源
- 免费且开源:遵循BSD许可证,可以自由使用于学术和商业项目
在Python生态系统中,OpenCV通过cv2
模块提供了丰富的API,结合Python的简洁语法和强大的科学计算库(如NumPy、SciPy等),使得复杂的图像处理任务变得简单易行。
1.2 安装与配置
在Python环境中安装OpenCV非常简单,可以通过pip包管理器完成:
# 安装基本版本
pip install opencv-python# 安装完整版本(包含contrib模块)
pip install opencv-contrib-python
推荐使用虚拟环境进行安装,以避免依赖冲突:
# 创建虚拟环境
python -m venv opencv-env# 激活虚拟环境(Windows)
opencv-env\Scripts\activate# 激活虚拟环境(Linux/macOS)
source opencv-env/bin/activate# 安装OpenCV及相关依赖
pip install opencv-contrib-python numpy matplotlib jupyter
安装完成后,可以通过以下代码验证安装是否成功:
import cv2
import numpy as np# 打印OpenCV版本
print(f"OpenCV版本: {cv2.__version__}")# 创建一个简单的图像
img = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.circle(img, (150, 150), 100, (0, 255, 0), 5)
cv2.putText(img, "Hello OpenCV", (60, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)# 显示图像
cv2.imshow("测试图像", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
如果能够看到一个包含绿色圆圈和"Hello OpenCV"文字的窗口,说明安装成功。
1.3 基本图像操作
在开始复杂的图像处理任务之前,我们需要先熟悉一些基本的图像操作,包括读取、显示、保存图像以及访问和修改像素值。
1.3.1 读取、显示和保存图像
import cv2# 读取图像
img = cv2.imread('example.jpg')# 检查图像是否正确读取
if img is None:print("无法读取图像")
else:# 显示图像cv2.imshow('原始图像', img)# 等待按键cv2.waitKey(0)# 保存图像cv2.imwrite('output.jpg', img)# 关闭所有窗口cv2.destroyAllWindows()
OpenCV使用BGR(蓝-绿-红)颜色顺序,而不是常见的RGB顺序,这是一个需要特别注意的点。
1.3.2 访问和修改像素
图像在OpenCV中表示为NumPy数组,这使得像素操作变得非常灵活:
import cv2
import numpy as np# 读取图像
img = cv2.imread('example.jpg')# 获取图像尺寸
height, width, channels = img.shape
print(f"图像尺寸: {width}x{height}, 通道数: {channels}")# 访问单个像素
pixel = img[100, 100]
print(f"坐标(100,100)处的像素值: {pixel}") # 返回[B, G, R]值# 修改单个像素
img[100, 100] = [0, 0, 255] # 设置为红色# 提取图像区域(ROI - Region of Interest)
roi = img[50:150, 50:150]
cv2.imshow('ROI', roi)# 修改图像区域
img[50:150, 50:150] = [0, 255, 0] # 将ROI区域设置为绿色# 显示修改后的图像
cv2.imshow('修改后的图像', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
1.3.3 图像通道操作
# 分离通道
b, g, r = cv2.split(img)# 显示单个通道(作为灰度图像)
cv2.imshow('蓝色通道', b)
cv2.imshow('绿色通道', g)
cv2.imshow('红色通道', r)# 合并通道
merged = cv2.merge([b, g, r])
cv2.imshow('合并后的图像', merged)# 只保留红色通道
red_only = img.copy()
red_only[:, :, 0] = 0 # 蓝色通道置零
red_only[:, :, 1] = 0 # 绿色通道置零
cv2.imshow('仅红色通道', red_only)cv2.waitKey(0)
cv2.destroyAllWindows()
1.3.4 图像几何变换
# 调整图像大小
resized = cv2.resize(img, (width//2, height//2)) # 缩小为原来的一半
cv2.imshow('缩放后的图像', resized)# 旋转图像
center = (width//2, height//2)
rotation_matrix = cv2.getRotationMatrix2D(center, 45, 1.0) # 旋转45度
rotated = cv2.warpAffine(img, rotation_matrix, (width, height))
cv2.imshow('旋转后的图像', rotated)# 平移图像
translation_matrix = np.float32([[1, 0, 50], [0, 1, 30]]) # x方向平移50,y方向平移30
translated = cv2.warpAffine(img, translation_matrix, (width, height))
cv2.imshow('平移后的图像', translated)# 翻转图像
flipped_h = cv2.flip(img, 1) # 水平翻转
flipped_v = cv2.flip(img, 0) # 垂直翻转
flipped_both = cv2.flip(img, -1) # 同时水平和垂直翻转cv2.imshow('水平翻转', flipped_h)
cv2.imshow('垂直翻转', flipped_v)
cv2.imshow('水平垂直翻转', flipped_both)cv2.waitKey(0)
cv2.destroyAllWindows()
通过这些基本操作,我们已经能够对图像进行读取、显示、保存以及基本的像素操作和几何变换。这些是进行更复杂图像处理的基础,在接下来的章节中,我们将在此基础上探索更高级的图像处理技术。
2. 图像处理基础
图像处理是计算机视觉的基础,通过对图像进行各种变换和增强,可以提取有用信息,去除噪声,或者为后续的高级分析做准备。本节将介绍一些基础但强大的图像处理技术。
2.1 色彩空间转换
色彩空间是描述颜色的数学模型。不同的色彩空间适用于不同的应用场景,OpenCV提供了丰富的色彩空间转换功能:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取彩色图像
img = cv2.imread('example.jpg')# BGR转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# BGR转换为HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)# BGR转换为Lab
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)# 显示不同色彩空间的图像
plt.figure(figsize=(12, 8))plt.subplot(221), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('原始图像 (RGB)'), plt.axis('off')plt.subplot(222), plt.imshow(gray, cmap='gray')
plt.title('灰度图'), plt.axis('off')plt.subplot(223), plt.imshow(hsv)
plt.title('HSV'), plt.axis('off')plt.subplot(224), plt.imshow(lab)
plt.title('Lab'), plt.axis('off')plt.tight_layout()
plt.show()
HSV色彩空间(色调-饱和度-明度)在许多计算机视觉应用中特别有用,例如基于颜色的目标检测和跟踪:
# 在HSV空间中提取特定颜色范围(例如红色)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)# 定义红色的HSV范围
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([180, 255, 255])# 创建掩码
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)# 应用掩码
red_only = cv2.bitwise_and(img, img, mask=mask)# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('红色掩码', mask)
cv2.imshow('提取的红色', red_only)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 图像处理基础(续)
2.2 图像滤波与平滑
图像滤波是图像处理中最基本也是最重要的操作之一,它可以用于去除噪声、模糊图像、增强边缘等。OpenCV提供了多种滤波器实现:
2.2.1 均值滤波
均值滤波是最简单的滤波器,它将每个像素替换为其邻域像素的平均值:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('noisy.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转换为RGB以便使用matplotlib显示# 添加一些噪声(如果图像本身没有噪声)
noisy = img.copy()
noise = np.random.normal(0, 15, img.shape).astype(np.uint8)
noisy = cv2.add(noisy, noise)# 应用均值滤波
blur_3x3 = cv2.blur(noisy, (3, 3))
blur_5x5 = cv2.blur(noisy, (5, 5))
blur_7x7 = cv2.blur(noisy, (7, 7))# 显示结果
plt.figure(figsize=(12, 10))plt.subplot(221), plt.imshow(img)
plt.title('原始图像'), plt.axis('off')plt.subplot(222), plt.imshow(noisy)
plt.title('添加噪声后'), plt.axis('off')plt.subplot(223), plt.imshow(blur_3x3)
plt.title('均值滤波 (3x3)'), plt.axis('off')plt.subplot(224), plt.imshow(blur_7x7)
plt.title('均值滤波 (7x7)'), plt.axis('off')plt.tight_layout()
plt.show()
2.2.2 高斯滤波
高斯滤波使用二维高斯函数作为卷积核,相比均值滤波,它能更好地保留图像细节:
# 应用高斯滤波
gaussian_3x3 = cv2.GaussianBlur(noisy, (3, 3), 0)
gaussian_5x5 = cv2.GaussianBlur(noisy, (5, 5), 0)
gaussian_7x7 = cv2.GaussianBlur(noisy, (7, 7), 0)plt.figure(figsize=(12, 10))plt.subplot(221), plt.imshow(noisy)
plt.title('噪声图像'), plt.axis('off')plt.subplot(222), plt.imshow(gaussian_3x3)
plt.title('高斯滤波 (3x3)'), plt.axis('off')plt.subplot(223), plt.imshow(gaussian_5x5)
plt.title('高斯滤波 (5x5)'), plt.axis('off')plt.subplot(224), plt.imshow(gaussian_7x7)
plt.title('高斯滤波 (7x7)'), plt.axis('off')plt.tight_layout()
plt.show()
2.2.3 中值滤波
中值滤波将每个像素替换为其邻域像素的中值,对于椒盐噪声特别有效:
# 添加椒盐噪声
salt_pepper = img.copy()
prob = 0.05
thres = 1 - prob
for i in range(img.shape[0]):for j in range(img.shape[1]):rdn = np.random.random()if rdn < prob:salt_pepper[i][j] = 0 # 黑色噪点elif rdn > thres:salt_pepper[i][j] = 255 # 白色噪点# 应用中值滤波
median_3x3 = cv2.medianBlur(salt_pepper, 3)
median_5x5 = cv2.medianBlur(salt_pepper, 5)plt.figure(figsize=(12, 8))plt.subplot(131), plt.imshow(salt_pepper)
plt.title('椒盐噪声'), plt.axis('off')plt.subplot(132), plt.imshow(median_3x3)
plt.title('中值滤波 (3x3)'), plt.axis('off')plt.subplot(133), plt.imshow(median_5x5)
plt.title('中值滤波 (5x5)'), plt.axis('off')plt.tight_layout()
plt.show()
2.2.4 双边滤波
双边滤波是一种保边滤波器,可以在平滑图像的同时保留边缘信息:
# 应用双边滤波
bilateral = cv2.bilateralFilter(noisy, 9, 75, 75)plt.figure(figsize=(12, 4))plt.subplot(131), plt.imshow(noisy)
plt.title('噪声图像'), plt.axis('off')plt.subplot(132), plt.imshow(gaussian_5x5)
plt.title('高斯滤波'), plt.axis('off')plt.subplot(133), plt.imshow(bilateral)
plt.title('双边滤波'), plt.axis('off')plt.tight_layout()
plt.show()
2.3 边缘检测
边缘检测是图像处理中的基本操作,用于识别图像中亮度变化明显的区域。
2.3.1 Sobel算子
Sobel算子通过计算图像的梯度来检测边缘:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('building.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 应用Sobel算子
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) # x方向梯度
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # y方向梯度# 计算梯度幅值
sobelx_abs = cv2.convertScaleAbs(sobelx) # 转换为uint8
sobely_abs = cv2.convertScaleAbs(sobely)
sobel_combined = cv2.addWeighted(sobelx_abs, 0.5, sobely_abs, 0.5, 0)plt.figure(figsize=(12, 8))plt.subplot(221), plt.imshow(gray, cmap='gray')
plt.title('原始灰度图'), plt.axis('off')plt.subplot(222), plt.imshow(sobelx_abs, cmap='gray')
plt.title('Sobel X方向'), plt.axis('off')plt.subplot(223), plt.imshow(sobely_abs, cmap='gray')
plt.title('Sobel Y方向'), plt.axis('off')plt.subplot(224), plt.imshow(sobel_combined, cmap='gray')
plt.title('Sobel 组合'), plt.axis('off')plt.tight_layout()
plt.show()
2.3.2 Canny边缘检测
Canny边缘检测是一种多阶段的边缘检测算法,能够提供更准确的边缘:
# 应用Canny边缘检测
edges_low = cv2.Canny(gray, 50, 150)
edges_high = cv2.Canny(gray, 100, 200)plt.figure(figsize=(12, 8))plt.subplot(131), plt.imshow(gray, cmap='gray')
plt.title('原始灰度图'), plt.axis('off')plt.subplot(132), plt.imshow(edges_low, cmap='gray')
plt.title('Canny (低阈值)'), plt.axis('off')plt.subplot(133), plt.imshow(edges_high, cmap='gray')
plt.title('Canny (高阈值)'), plt.axis('off')plt.tight_layout()
plt.show()
2.4 图像增强与修复
图像增强技术可以改善图像的视觉效果,而图像修复则可以恢复损坏的图像区域。
2.4.1 直方图均衡化
直方图均衡化可以增强图像的对比度:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('low_contrast.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 应用直方图均衡化
equalized = cv2.equalizeHist(gray)# 显示结果和直方图
plt.figure(figsize=(12, 8))plt.subplot(221), plt.imshow(gray, cmap='gray')
plt.title('原始灰度图'), plt.axis('off')plt.subplot(222), plt.imshow(equalized, cmap='gray')
plt.title('直方图均衡化后'), plt.axis('off')plt.subplot(223), plt.hist(gray.ravel(), 256, [0, 256])
plt.title('原始直方图')plt.subplot(224), plt.hist(equalized.ravel(), 256, [0, 256])
plt.title('均衡化后直方图')plt.tight_layout()
plt.show()
对于彩色图像,可以在HSV色彩空间中只对V通道进行均衡化:
# 彩色图像的直方图均衡化
img_color = cv2.imread('low_contrast.jpg')
img_color_rgb = cv2.cvtColor(img_color, cv2.COLOR_BGR2RGB)# 转换到HSV色彩空间
hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)# 只对亮度通道进行均衡化
v_equalized = cv2.equalizeHist(v)
hsv_equalized = cv2.merge([h, s, v_equalized])
result = cv2.cvtColor(hsv_equalized, cv2.COLOR_HSV2RGB)plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(img_color_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(122), plt.imshow(result)
plt.title('亮度通道均衡化后'), plt.axis('off')plt.tight_layout()
plt.show()
2.4.2 自适应直方图均衡化
对于光照不均匀的图像,自适应直方图均衡化(CLAHE)通常效果更好:
# 应用CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
clahe_result = clahe.apply(gray)plt.figure(figsize=(12, 4))plt.subplot(131), plt.imshow(gray, cmap='gray')
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(equalized, cmap='gray')
plt.title('全局直方图均衡化'), plt.axis('off')plt.subplot(133), plt.imshow(clahe_result, cmap='gray')
plt.title('CLAHE'), plt.axis('off')plt.tight_layout()
plt.show()
2.4.3 图像修复(Inpainting)
图像修复技术可以用于修复图像中的损坏区域或移除不需要的对象:
# 读取图像
img = cv2.imread('damaged.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 创建掩码(在实际应用中,这通常是手动标记或自动检测的)
mask = np.zeros(img.shape[:2], np.uint8)
# 假设我们要修复图像中间的矩形区域
cv2.rectangle(mask, (100, 100), (200, 200), 255, -1)# 应用修复算法
dst_telea = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)
dst_ns = cv2.inpaint(img, mask, 3, cv2.INPAINT_NS)dst_telea_rgb = cv2.cvtColor(dst_telea, cv2.COLOR_BGR2RGB)
dst_ns_rgb = cv2.cvtColor(dst_ns, cv2.COLOR_BGR2RGB)plt.figure(figsize=(12, 8))plt.subplot(221), plt.imshow(img_rgb)
plt.title('损坏的图像'), plt.axis('off')plt.subplot(222), plt.imshow(mask, cmap='gray')
plt.title('掩码'), plt.axis('off')plt.subplot(223), plt.imshow(dst_telea_rgb)
plt.title('Telea方法修复'), plt.axis('off')plt.subplot(224), plt.imshow(dst_ns_rgb)
plt.title('Navier-Stokes方法修复'), plt.axis('off')plt.tight_layout()
plt.show()
3. 特征提取与描述
特征提取是计算机视觉中的关键步骤,它将图像中的视觉信息转换为数值特征,这些特征可以用于图像匹配、对象识别等任务。
3.1 传统特征提取方法
3.1.1 Harris角点检测
角点是图像中梯度在多个方向上变化明显的点,常用于特征匹配和跟踪:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('building.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 应用Harris角点检测
dst = cv2.cornerHarris(gray, 2, 3, 0.04)# 结果归一化
dst_norm = np.empty(dst.shape, dtype=np.float32)
cv2.normalize(dst, dst_norm, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX)# 阈值处理
threshold = 0.01 * dst_norm.max()
harris_corners = img_rgb.copy()# 标记角点
for i in range(dst_norm.shape[0]):for j in range(dst_norm.shape[1]):if dst_norm[i, j] > threshold:cv2.circle(harris_corners, (j, i), 3, (0, 255, 0), -1)plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(122), plt.imshow(harris_corners)
plt.title('Harris角点检测'), plt.axis('off')plt.tight_layout()
plt.show()
3.1.2 Shi-Tomasi角点检测
Shi-Tomasi角点检测是Harris角点检测的改进版本:
# 应用Shi-Tomasi角点检测
corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10)
corners = np.int0(corners)shi_tomasi_corners = img_rgb.copy()
for corner in corners:x, y = corner.ravel()cv2.circle(shi_tomasi_corners, (x, y), 3, (255, 0, 0), -1)plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(harris_corners)
plt.title('Harris角点'), plt.axis('off')plt.subplot(122), plt.imshow(shi_tomasi_corners)
plt.title('Shi-Tomasi角点'), plt.axis('off')plt.tight_layout()
plt.show()
3.1.3 SIFT特征提取
SIFT (Scale-Invariant Feature Transform) 是一种尺度不变特征变换算法,能够检测和描述图像中的局部特征:
# 创建SIFT检测器
sift = cv2.SIFT_create()# 检测关键点和计算描述符
keypoints, descriptors = sift.detectAndCompute(gray, None)# 绘制关键点
sift_img = cv2.drawKeypoints(img_rgb, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)plt.figure(figsize=(12, 6))
plt.imshow(sift_img)
plt.title(f'SIFT特征 ({len(keypoints)}个关键点)'), plt.axis('off')
plt.show()
3.1.4 SURF特征提取
SURF (Speeded-Up Robust Features) 是SIFT的加速版本:
# 注意:SURF在较新版本的OpenCV中已被移至contrib模块
# 需要安装opencv-contrib-python
try:# 创建SURF检测器surf = cv2.xfeatures2d.SURF_create(400)# 检测关键点和计算描述符keypoints, descriptors = surf.detectAndCompute(gray, None)# 绘制关键点surf_img = cv2.drawKeypoints(img_rgb, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)plt.figure(figsize=(12, 6))plt.imshow(surf_img)plt.title(f'SURF特征 ({len(keypoints)}个关键点)'), plt.axis('off')plt.show()
except:print("SURF不可用,请安装opencv-contrib-python")
3.1.5 ORB特征提取
ORB (Oriented FAST and Rotated BRIEF) 是一种计算效率高且免费的特征提取算法:
# 创建ORB检测器
orb = cv2.ORB_create()# 检测关键点和计算描述符
keypoints, descriptors = orb.detectAndCompute(gray, None)# 绘制关键点
orb_img = cv2.drawKeypoints(img_rgb, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)plt.figure(figsize=(12, 6))
plt.imshow(orb_img)
plt.title(f'ORB特征 ({len(keypoints)}个关键点)'), plt.axis('off')
plt.show()
3.2 颜色特征提取
颜色是图像中最基本也是最直观的特征,颜色特征提取在图像检索、分类和分割等任务中有广泛应用。
3.2.1 颜色直方图
颜色直方图是描述图像颜色分布的统计特征:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('colorful.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 计算RGB颜色直方图
color = ('r', 'g', 'b')
plt.figure(figsize=(12, 6))for i, col in enumerate(color):hist = cv2.calcHist([img], [i], None, [256], [0, 256])plt.plot(hist, color=col)plt.xlim([0, 256])plt.title('RGB颜色直方图')
plt.xlabel('像素值')
plt.ylabel('频率')
plt.grid()
plt.show()# 显示原始图像
plt.figure(figsize=(8, 6))
plt.imshow(img_rgb)
plt.title('原始图像')
plt.axis('off')
plt.show()
3.2.2 颜色矩特征
颜色矩是描述图像颜色分布的统计量,包括一阶矩(均值)、二阶矩(标准差)和三阶矩(偏度):
def color_moments(img):"""计算图像的颜色矩特征"""# 将图像分割为RGB三个通道img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)r, g, b = cv2.split(img)# 计算每个通道的矩channels = [r, g, b]moments = []for channel in channels:# 一阶矩(均值)mean = np.mean(channel)# 二阶矩(标准差)std = np.std(channel)# 三阶矩(偏度)skewness = np.mean(((channel - mean) / (std + 1e-8)) ** 3)moments.extend([mean, std, skewness])return moments# 计算颜色矩特征
moments = color_moments(img)# 显示结果
print("颜色矩特征(9维向量):")
print("R通道: 均值={:.2f}, 标准差={:.2f}, 偏度={:.2f}".format(moments[0], moments[1], moments[2]))
print("G通道: 均值={:.2f}, 标准差={:.2f}, 偏度={:.2f}".format(moments[3], moments[4], moments[5]))
print("B通道: 均值={:.2f}, 标准差={:.2f}, 偏度={:.2f}".format(moments[6], moments[7], moments[8]))
3.2.3 主要颜色提取
使用K-means聚类算法可以提取图像中的主要颜色:
def extract_dominant_colors(img, k=5):"""使用K-means提取图像中的主要颜色"""# 将图像转换为RGBimg_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 将图像重塑为像素列表pixels = img_rgb.reshape(-1, 3).astype(np.float32)# 定义K-means参数criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)# 应用K-means_, labels, centers = cv2.kmeans(pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)# 计算每个聚类的像素数量counts = np.bincount(labels.flatten())# 按像素数量排序聚类中心sorted_indices = np.argsort(counts)[::-1]sorted_centers = centers[sorted_indices]sorted_counts = counts[sorted_indices]# 计算每个颜色的百分比total_pixels = img_rgb.shape[0] * img_rgb.shape[1]percentages = (sorted_counts / total_pixels) * 100return sorted_centers.astype(np.uint8), percentages# 提取主要颜色
dominant_colors, percentages = extract_dominant_colors(img, k=5)# 显示主要颜色
plt.figure(figsize=(12, 6))# 显示原始图像
plt.subplot(121)
plt.imshow(img_rgb)
plt.title('原始图像')
plt.axis('off')# 显示主要颜色
plt.subplot(122)
for i, (color, percentage) in enumerate(zip(dominant_colors, percentages)):plt.bar(i, percentage, color=color/255, width=0.8)plt.text(i, percentage + 1, f"{percentage:.1f}%", ha='center')plt.title('主要颜色分布')
plt.xlabel('颜色索引')
plt.ylabel('百分比 (%)')
plt.ylim(0, 100)
plt.xticks([])plt.tight_layout()
plt.show()
3.3 纹理特征提取
纹理特征描述了图像区域的空间排列和局部像素变化,对于区分不同材质的表面非常有用。
3.3.1 局部二值模式(LBP)
LBP是一种简单但有效的纹理描述符:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage.feature import local_binary_pattern# 读取图像并转换为灰度
img = cv2.imread('texture.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 计算LBP特征
radius = 3
n_points = 8 * radius
lbp = local_binary_pattern(gray, n_points, radius, method='uniform')# 计算LBP直方图
n_bins = int(lbp.max() + 1)
hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins))
hist = hist.astype("float")
hist /= (hist.sum() + 1e-7) # 归一化# 显示结果
plt.figure(figsize=(12, 8))plt.subplot(221), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(222), plt.imshow(lbp, cmap='gray')
plt.title('LBP特征图'), plt.axis('off')plt.subplot(223), plt.bar(range(len(hist)), hist)
plt.title('LBP直方图')
plt.xlabel('LBP值')
plt.ylabel('频率')plt.tight_layout()
plt.show()
3.3.2 Gabor纹理特征
Gabor滤波器可以提取不同尺度和方向上的纹理特征:
def gabor_filter(img, ksize=31, sigma=5, theta=0, lambd=10, gamma=0.5, psi=0):"""应用Gabor滤波器"""kern = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, psi, ktype=cv2.CV_32F)kern /= 1.5 * kern.sum()filtered = cv2.filter2D(img, cv2.CV_8UC3, kern)return filtered, kern# 读取图像并转换为灰度
img = cv2.imread('texture.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 应用不同方向的Gabor滤波器
thetas = [0, np.pi/4, np.pi/2, 3*np.pi/4]
filtered_images = []
kernels = []for theta in thetas:filtered, kernel = gabor_filter(gray, theta=theta)filtered_images.append(filtered)kernels.append(kernel)# 显示结果
plt.figure(figsize=(15, 10))plt.subplot(251), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')for i, (filtered, kernel, theta) in enumerate(zip(filtered_images, kernels, thetas)):angle = int(theta * 180 / np.pi)plt.subplot(252 + i), plt.imshow(kernel, cmap='gray')plt.title(f'Gabor核 ({angle}°)'), plt.axis('off')plt.subplot(256 + i), plt.imshow(filtered, cmap='gray')plt.title(f'Gabor滤波 ({angle}°)'), plt.axis('off')plt.tight_layout()
plt.show()
3.4 特征匹配与应用
特征匹配是将两幅图像中的特征点进行对应的过程,常用于图像拼接、对象识别和跟踪等应用。
3.4.1 特征匹配基础
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取两幅图像
img1 = cv2.imread('object.jpg')
img2 = cv2.imread('scene.jpg')img1_rgb = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
img2_rgb = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)# 转换为灰度图
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)# 使用ORB检测特征点并计算描述符
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(gray1, None)
kp2, des2 = orb.detectAndCompute(gray2, None)# 使用暴力匹配器进行特征匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)# 绘制前30个匹配
img_matches = cv2.drawMatches(img1_rgb, kp1, img2_rgb, kp2, matches[:30], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)plt.figure(figsize=(15, 8))
plt.imshow(img_matches)
plt.title(f'ORB特征匹配 (前30个最佳匹配,共{len(matches)}个)')
plt.axis('off')
plt.show()
3.4.2 FLANN匹配器
对于大规模特征匹配,FLANN (Fast Library for Approximate Nearest Neighbors) 比暴力匹配更高效:
# 使用SIFT特征
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)# FLANN参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)# 使用FLANN匹配器
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)# 应用Lowe's比率测试
good_matches = []
for m, n in matches:if m.distance < 0.7 * n.distance:good_matches.append(m)# 绘制匹配
img_matches = cv2.drawMatches(img1_rgb, kp1, img2_rgb, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)plt.figure(figsize=(15, 8))
plt.imshow(img_matches)
plt.title(f'SIFT特征 + FLANN匹配 ({len(good_matches)}个好的匹配)')
plt.axis('off')
plt.show()
3.4.3 对象定位
使用特征匹配可以在场景中定位目标对象:
# 如果有足够的好匹配点
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)# 使用RANSAC算法计算单应性矩阵H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)# 获取图像1的尺寸h, w = gray1.shape# 定义图像1的四个角点pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2)# 使用单应性矩阵转换角点坐标dst = cv2.perspectiveTransform(pts, H)# 在图像2上绘制边界框img2_with_box = img2_rgb.copy()img2_with_box = cv2.polylines(img2_with_box, [np.int32(dst)], True, (0, 255, 0), 3)plt.figure(figsize=(15, 8))plt.imshow(img2_with_box)plt.title('对象定位结果')plt.axis('off')plt.show()
else:print("没有足够的好匹配点来定位对象")
4. 图像分割技术
图像分割是将图像划分为多个区域或对象的过程,是许多计算机视觉应用的基础步骤。
4.1 基于阈值的分割
阈值分割是最简单的图像分割方法,它将图像中的像素根据其灰度值分为前景和背景。
4.1.1 全局阈值
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('object.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 应用全局阈值
ret, thresh1 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO_INV)# 显示结果
titles = ['原始图像', '二值化', '反二值化', '截断', 'TOZERO', 'TOZERO_INV']
images = [img_rgb, thresh1, thresh2, thresh3, thresh4, thresh5]plt.figure(figsize=(15, 8))
for i in range(6):plt.subplot(2, 3, i+1)if i == 0:plt.imshow(images[i])else:plt.imshow(images[i], cmap='gray')plt.title(titles[i])plt.axis('off')plt.tight_layout()
plt.show()
4.1.2 自适应阈值
对于光照不均匀的图像,自适应阈值通常效果更好:
# 应用自适应阈值
adaptive_mean = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
adaptive_gaussian = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)# 显示结果
plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(gray, cmap='gray')
plt.title('原始灰度图'), plt.axis('off')plt.subplot(132), plt.imshow(adaptive_mean, cmap='gray')
plt.title('自适应均值阈值'), plt.axis('off')plt.subplot(133), plt.imshow(adaptive_gaussian, cmap='gray')
plt.title('自适应高斯阈值'), plt.axis('off')plt.tight_layout()
plt.show()
4.1.3 Otsu阈值法
Otsu阈值法可以自动确定最佳阈值:
# 应用Otsu阈值法
ret, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 显示结果
plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(gray, cmap='gray')
plt.title('灰度图'), plt.axis('off')plt.subplot(133), plt.imshow(otsu, cmap='gray')
plt.title(f'Otsu阈值法 (阈值={ret})'), plt.axis('off')plt.tight_layout()
plt.show()
4.2 基于边缘的分割
边缘检测是图像分割的重要方法,通过识别图像中的边缘来划分不同区域。
4.2.1 Canny边缘检测与轮廓提取
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 应用高斯模糊减少噪声
blurred = cv2.GaussianBlur(gray, (5, 5), 0)# 应用Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)# 查找轮廓
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 绘制轮廓
contour_img = img_rgb.copy()
cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)# 显示结果
plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(edges, cmap='gray')
plt.title('Canny边缘检测'), plt.axis('off')plt.subplot(133), plt.imshow(contour_img)
plt.title(f'轮廓提取 ({len(contours)}个轮廓)'), plt.axis('off')plt.tight_layout()
plt.show()
4.2.2 形状识别
通过分析轮廓的几何特性,可以识别图像中的基本形状:
# 形状识别
shape_img = img_rgb.copy()for cnt in contours:# 计算轮廓的周长perimeter = cv2.arcLength(cnt, True)# 多边形近似approx = cv2.approxPolyDP(cnt, 0.04 * perimeter, True)# 计算轮廓的中心M = cv2.moments(cnt)if M["m00"] != 0:cx = int(M["m10"] / M["m00"])cy = int(M["m01"] / M["m00"])else:cx, cy = 0, 0# 根据顶点数量识别形状if len(approx) == 3:shape = "三角形"elif len(approx) == 4:# 计算边界矩形x, y, w, h = cv2.boundingRect(approx)aspect_ratio = float(w) / h# 判断是正方形还是矩形if 0.95 <= aspect_ratio <= 1.05:shape = "正方形"else:shape = "矩形"elif len(approx) == 5:shape = "五边形"elif len(approx) == 6:shape = "六边形"elif len(approx) > 10:shape = "圆形"else:shape = "未知"# 绘制形状名称cv2.putText(shape_img, shape, (cx - 20, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)# 绘制轮廓cv2.drawContours(shape_img, [approx], 0, (0, 255, 0), 2)plt.figure(figsize=(10, 8))
plt.imshow(shape_img)
plt.title('形状识别结果')
plt.axis('off')
plt.show()
4.3 基于区域的分割
基于区域的分割方法通过相似性准则将相邻像素分组成区域。
4.3.1 分水岭算法
分水岭算法是一种基于区域的分割方法,特别适用于分离接触的对象:
# 读取图像并转换为灰度
img = cv2.imread('coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 应用Otsu阈值法获取二值图像
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)# 噪声去除
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)# 确定背景区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)# 确定前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)# 查找未知区域
unknown = cv2.subtract(sure_bg, sure_fg)# 标记
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0# 应用分水岭算法
markers = cv2.watershed(img, markers)
img_rgb[markers == -1] = [255, 0, 0] # 标记边界为红色# 显示结果
plt.figure(figsize=(15, 10))plt.subplot(231), plt.imshow(img_rgb)
plt.title('分水岭分割结果'), plt.axis('off')plt.subplot(232), plt.imshow(thresh, cmap='gray')
plt.title('二值图像'), plt.axis('off')plt.subplot(233), plt.imshow(sure_bg, cmap='gray')
plt.title('确定的背景'), plt.axis('off')plt.subplot(234), plt.imshow(dist_transform, cmap='jet')
plt.title('距离变换'), plt.axis('off')plt.subplot(235), plt.imshow(sure_fg, cmap='gray')
plt.title('确定的前景'), plt.axis('off')plt.subplot(236), plt.imshow(markers, cmap='jet')
plt.title('标记'), plt.axis('off')plt.tight_layout()
plt.show()
4.3.2 GrabCut算法
GrabCut是一种交互式分割算法,可以通过最小的用户交互实现前景提取:
# 读取图像
img = cv2.imread('person.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 创建掩码、背景模型和前景模型
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)# 定义矩形区域 (x, y, width, height)
rect = (50, 50, img.shape[1]-100, img.shape[0]-100)# 应用GrabCut算法
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)# 修改掩码:0和2表示背景,1和3表示前景
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')# 获取前景
img_fg = img * mask2[:, :, np.newaxis]
img_fg_rgb = cv2.cvtColor(img_fg, cv2.COLOR_BGR2RGB)plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(122), plt.imshow(img_fg_rgb)
plt.title('GrabCut前景提取'), plt.axis('off')plt.tight_layout()
plt.show()
4.4 基于深度学习的分割
深度学习方法,特别是卷积神经网络(CNN),已经在图像分割任务中取得了显著的成果。
4.4.1 使用预训练模型进行语义分割
# 注意:此代码需要安装tensorflow和tensorflow-hub
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import cv2
import matplotlib.pyplot as plt# 加载预训练的DeepLab模型
model = hub.load('https://tfhub.dev/tensorflow/deeplabv3/1')# 读取图像
img = cv2.imread('street.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 调整图像大小
input_tensor = tf.convert_to_tensor(img_rgb)
input_tensor = input_tensor[tf.newaxis, ...]# 运行模型
results = model(input_tensor)# 提取分割掩码
seg_map = tf.argmax(results['logits'], axis=-1)
seg_map = tf.squeeze(seg_map).numpy()# 可视化分割结果
plt.figure(figsize=(15, 5))plt.subplot(121), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(122), plt.imshow(seg_map, cmap='jet')
plt.title('语义分割结果'), plt.axis('off')plt.tight_layout()
plt.show()
5. 目标检测与识别
目标检测是计算机视觉中的一项关键任务,它不仅需要识别图像中的对象,还需要定位它们的位置。
5.1 传统机器学习方法
5.1.1 Haar级联分类器
Haar级联分类器是一种基于Haar特征的机器学习方法,广泛用于人脸检测等任务:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('group.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 加载预训练的人脸检测器
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')# 检测人脸
faces = face_cascade.detectMultiScale(gray, 1.3, 5)# 在图像上标记人脸和眼睛
img_with_faces = img_rgb.copy()
for (x, y, w, h) in faces:cv2.rectangle(img_with_faces, (x, y), (x+w, y+h), (255, 0, 0), 2)roi_gray = gray[y:y+h, x:x+w]roi_color = img_with_faces[y:y+h, x:x+w]# 在每个人脸区域内检测眼睛eyes = eye_cascade.detectMultiScale(roi_gray)for (ex, ey, ew, eh) in eyes:cv2.rectangle(roi_color, (ex, ey), (ex+ew, ey+eh), (0, 255, 0), 2)plt.figure(figsize=(12, 8))
plt.imshow(img_with_faces)
plt.title(f'人脸检测结果 (检测到{len(faces)}张人脸)')
plt.axis('off')
plt.show()
5.2 深度学习方法
深度学习,特别是卷积神经网络(CNN),已经在目标检测和识别任务中取得了突破性的进展。
5.2.1 使用预训练模型进行目标检测
import cv2
import numpy as np
import matplotlib.pyplot as plt# 加载预训练的MobileNet SSD模型
net = cv2.dnn.readNetFromCaffe('MobileNetSSD_deploy.prototxt.txt','MobileNetSSD_deploy.caffemodel'
)# COCO数据集的类别
classes = ["background", "aeroplane", "bicycle", "bird", "boat","bottle", "bus", "car", "cat", "chair", "cow", "diningtable","dog", "horse", "motorbike", "person", "pottedplant", "sheep","sofa", "train", "tvmonitor"]# 为每个类别生成不同的颜色
colors = np.random.uniform(0, 255, size=(len(classes), 3))# 读取图像
img = cv2.imread('street_scene.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
height, width = img.shape[:2]# 预处理图像
blob = cv2.dnn.blobFromImage(img, 0.007843, (300, 300), 127.5)# 设置网络输入
net.setInput(blob)# 前向传播
detections = net.forward()# 处理检测结果
result_img = img_rgb.copy()
for i in range(detections.shape[2]):confidence = detections[0, 0, i, 2]# 过滤低置信度的检测if confidence > 0.5:class_id = int(detections[0, 0, i, 1])# 计算边界框坐标box = detections[0, 0, i, 3:7] * np.array([width, height, width, height])(startX, startY, endX, endY) = box.astype("int")# 绘制边界框和标签label = f"{classes[class_id]}: {confidence:.2f}"cv2.rectangle(result_img, (startX, startY), (endX, endY), colors[class_id].tolist(), 2)y = startY - 15 if startY - 15 > 15 else startY + 15cv2.putText(result_img, label, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[class_id].tolist(), 2)plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.title('MobileNet SSD目标检测结果')
plt.axis('off')
plt.show()
5.2.2 使用YOLO进行目标检测
YOLO (You Only Look Once) 是一种流行的实时目标检测系统:
import cv2
import numpy as np
import matplotlib.pyplot as plt# 加载YOLO模型
net = cv2.dnn.readNetFromDarknet('yolov3.cfg', 'yolov3.weights')# 获取输出层名称
layer_names = net.getLayerNames()
output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers().flatten()]# COCO数据集的类别
with open('coco.names', 'r') as f:classes = [line.strip() for line in f.readlines()]# 为每个类别生成不同的颜色
colors = np.random.uniform(0, 255, size=(len(classes), 3))# 读取图像
img = cv2.imread('street_scene.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
height, width = img.shape[:2]# 预处理图像
blob = cv2.dnn.blobFromImage(img, 1/255.0, (416, 416), swapRB=True, crop=False)# 设置网络输入
net.setInput(blob)# 前向传播
outs = net.forward(output_layers)# 处理检测结果
class_ids = []
confidences = []
boxes = []for out in outs:for detection in out:scores = detection[5:]class_id = np.argmax(scores)confidence = scores[class_id]if confidence > 0.5:# 目标中心坐标和宽高center_x = int(detection[0] * width)center_y = int(detection[1] * height)w = int(detection[2] * width)h = int(detection[3] * height)# 边界框坐标x = int(center_x - w / 2)y = int(center_y - h / 2)boxes.append([x, y, w, h])confidences.append(float(confidence))class_ids.append(class_id)# 非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)# 绘制检测结果
result_img = img_rgb.copy()
if len(indices) > 0:for i in indices.flatten():x, y, w, h = boxes[i]label = f"{classes[class_ids[i]]}: {confidences[i]:.2f}"color = colors[class_ids[i]].tolist()cv2.rectangle(result_img, (x, y), (x + w, y + h), color, 2)cv2.putText(result_img, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.title('YOLO目标检测结果')
plt.axis('off')
plt.show()
5.3 实时目标检测应用
将目标检测应用于视频流,实现实时目标检测:
import cv2
import numpy as np
import time# 加载YOLO模型
net = cv2.dnn.readNetFromDarknet('yolov3.cfg', 'yolov3.weights')
layer_names = net.getLayerNames()
output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers().flatten()]# 加载类别名称
with open('coco.names', 'r') as f:classes = [line.strip() for line in f.readlines()]
colors = np.random.uniform(0, 255, size=(len(classes), 3))# 打开摄像头
cap = cv2.VideoCapture(0)while True:# 读取帧ret, frame = cap.read()if not ret:breakheight, width = frame.shape[:2]# 预处理图像blob = cv2.dnn.blobFromImage(frame, 1/255.0, (416, 416), swapRB=True, crop=False)# 设置网络输入net.setInput(blob)# 计算FPSstart_time = time.time()# 前向传播outs = net.forward(output_layers)# 计算FPSend_time = time.time()fps = 1 / (end_time - start_time)# 处理检测结果class_ids = []confidences = []boxes = []for out in outs:for detection in out:scores = detection[5:]class_id = np.argmax(scores)confidence = scores[class_id]if confidence > 0.5:# 目标中心坐标和宽高center_x = int(detection[0] * width)center_y = int(detection[1] * height)w = int(detection[2] * width)h = int(detection[3] * height)# 边界框坐标x = int(center_x - w / 2)y = int(center_y - h / 2)boxes.append([x, y, w, h])confidences.append(float(confidence))class_ids.append(class_id)# 非极大值抑制indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)# 绘制检测结果if len(indices) > 0:for i in indices.flatten():x, y, w, h = boxes[i]label = f"{classes[class_ids[i]]}: {confidences[i]:.2f}"color = colors[class_ids[i]].tolist()cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)cv2.putText(frame, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)# 显示FPScv2.putText(frame, f"FPS: {fps:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)# 显示结果cv2.imshow('Real-time Object Detection', frame)# 按'q'退出if cv2.waitKey(1) & 0xFF == ord('q'):break# 释放资源
cap.release()
cv2.destroyAllWindows()
6. 实战项目与应用案例
在本节中,我们将通过几个实际应用案例,展示如何将前面学习的图像处理和计算机视觉技术应用到实际问题中。
6.1 文档扫描与矫正
文档扫描是一个常见的应用场景,它涉及到边缘检测、透视变换等技术。
import cv2
import numpy as np
import matplotlib.pyplot as pltdef order_points(pts):"""按照左上、右上、右下、左下的顺序排列坐标点"""rect = np.zeros((4, 2), dtype="float32")# 左上角点的x+y坐标和最小,右下角点的x+y坐标和最大s = pts.sum(axis=1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 右上角点的x-y差最小,左下角点的x-y差最大diff = np.diff(pts, axis=1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef four_point_transform(image, pts):"""对图像应用透视变换"""rect = order_points(pts)(tl, tr, br, bl) = rect# 计算新图像的宽度widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))# 计算新图像的高度heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 设置目标坐标dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype="float32")# 计算透视变换矩阵并应用M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))return warped# 读取图像
img = cv2.imread('document.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
orig = img.copy()# 预处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)# 查找轮廓
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]# 找到文档的轮廓
document_contour = None
for c in contours:# 计算轮廓的周长peri = cv2.arcLength(c, True)# 多边形近似approx = cv2.approxPolyDP(c, 0.02 * peri, True)# 如果近似轮廓有四个点,则认为找到了文档if len(approx) == 4:document_contour = approxbreak# 应用透视变换
if document_contour is not None:# 绘制轮廓cv2.drawContours(img, [document_contour], -1, (0, 255, 0), 2)# 应用透视变换warped = four_point_transform(orig, document_contour.reshape(4, 2))warped_rgb = cv2.cvtColor(warped, cv2.COLOR_BGR2RGB)# 显示结果plt.figure(figsize=(15, 10))plt.subplot(221), plt.imshow(img_rgb)plt.title('原始图像'), plt.axis('off')plt.subplot(222), plt.imshow(edged, cmap='gray')plt.title('边缘检测'), plt.axis('off')plt.subplot(223), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))plt.title('检测到的文档'), plt.axis('off')plt.subplot(224), plt.imshow(warped_rgb)plt.title('透视变换后'), plt.axis('off')plt.tight_layout()plt.show()
else:print("未能检测到文档的四个角点")
6.2 人脸检测与识别
人脸检测和识别是计算机视觉中的经典应用,广泛用于安全系统、用户认证等场景。
6.2.1 人脸检测与关键点定位
import cv2
import dlib
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('faces.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 初始化dlib的人脸检测器和关键点预测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')# 检测人脸
faces = detector(img_rgb)
print(f"检测到{len(faces)}张人脸")# 在图像上标记人脸和关键点
result_img = img_rgb.copy()
for face in faces:# 绘制人脸矩形x1, y1, x2, y2 = face.left(), face.top(), face.right(), face.bottom()cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)# 预测关键点landmarks = predictor(img_rgb, face)# 绘制68个关键点for n in range(0, 68):x = landmarks.part(n).xy = landmarks.part(n).ycv2.circle(result_img, (x, y), 2, (255, 0, 0), -1)# 显示结果
plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.title('人脸检测与关键点定位')
plt.axis('off')
plt.show()
6.2.2 人脸识别
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
from sklearn.metrics.pairwise import cosine_similarity# 加载人脸识别模型
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
face_recognizer.read('lbph_model.yml') # 假设已经训练好的模型# 加载人脸检测器
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')# 读取测试图像
img = cv2.imread('test_face.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 检测人脸
faces = face_cascade.detectMultiScale(gray, 1.3, 5)# 识别人脸
result_img = img_rgb.copy()
for (x, y, w, h) in faces:# 提取人脸区域face_roi = gray[y:y+h, x:x+w]# 调整大小(如果需要)face_roi = cv2.resize(face_roi, (100, 100))# 预测label, confidence = face_recognizer.predict(face_roi)# 获取对应的名字(假设有一个标签到名字的映射)names = ['Person1', 'Person2', 'Person3'] # 示例name = names[label] if label < len(names) else "Unknown"# 绘制结果cv2.rectangle(result_img, (x, y), (x+w, y+h), (0, 255, 0), 2)text = f"{name}: {confidence:.2f}"cv2.putText(result_img, text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)# 显示结果
plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.title('人脸识别结果')
plt.axis('off')
plt.show()
6.3 车牌识别系统
车牌识别是一个综合应用,涉及到目标检测、字符分割和光学字符识别(OCR)等技术。
import cv2
import numpy as np
import matplotlib.pyplot as plt
import pytesseract# 设置Tesseract OCR路径(如果需要)
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'def preprocess_plate(plate_img):"""预处理车牌图像以提高OCR准确性"""# 转换为灰度图gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY)# 应用高斯模糊blur = cv2.GaussianBlur(gray, (5, 5), 0)# 自适应阈值处理binary = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)# 形态学操作kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))morph = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)return morph# 读取图像
img = cv2.imread('car.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 预处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blur, 50, 200)# 查找轮廓
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 筛选可能的车牌区域
candidates = []
for contour in contours:# 计算轮廓的外接矩形(x, y, w, h) = cv2.boundingRect(contour)aspect_ratio = w / float(h)# 车牌的宽高比通常在2.0到5.5之间if 2.0 < aspect_ratio < 5.5 and w > 100 and h > 20:candidates.append((x, y, w, h))# 处理候选区域
result_img = img_rgb.copy()
for (x, y, w, h) in candidates:# 提取车牌区域plate_img = img[y:y+h, x:x+w]# 预处理车牌图像processed_plate = preprocess_plate(plate_img)# 使用OCR识别车牌号码plate_text = pytesseract.image_to_string(processed_plate, config='--psm 7')plate_text = ''.join(e for e in plate_text if e.isalnum()) # 只保留字母和数字# 绘制结果cv2.rectangle(result_img, (x, y), (x+w, y+h), (0, 255, 0), 2)cv2.putText(result_img, plate_text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)# 显示处理过程plt.figure(figsize=(15, 10))plt.subplot(221), plt.imshow(img_rgb)plt.title('原始图像'), plt.axis('off')plt.subplot(222), plt.imshow(edged, cmap='gray')plt.title('边缘检测'), plt.axis('off')plt.subplot(223), plt.imshow(cv2.cvtColor(plate_img, cv2.COLOR_BGR2RGB))plt.title('提取的车牌'), plt.axis('off')plt.subplot(224), plt.imshow(processed_plate, cmap='gray')plt.title('预处理后的车牌'), plt.axis('off')plt.tight_layout()plt.show()print(f"识别的车牌号码: {plate_text}")# 显示最终结果
plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.title('车牌识别结果')
plt.axis('off')
plt.show()
6.4 医学图像分析
医学图像分析是计算机视觉在医疗领域的重要应用,可以辅助医生进行疾病诊断和治疗规划。
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage import measure
from skimage.filters import threshold_otsu# 读取医学图像(例如CT扫描)
img = cv2.imread('medical_scan.jpg', 0) # 直接读取为灰度图# 应用高斯模糊减少噪声
blurred = cv2.GaussianBlur(img, (5, 5), 0)# 使用Otsu阈值法进行分割
thresh = threshold_otsu(blurred)
binary = blurred > thresh# 标记连通区域
labeled_img = measure.label(binary)
props = measure.regionprops(labeled_img)# 筛选感兴趣的区域(例如,面积大于一定阈值的区域)
min_area = 100
filtered_regions = [prop for prop in props if prop.area > min_area]# 可视化结果
plt.figure(figsize=(15, 10))plt.subplot(221), plt.imshow(img, cmap='gray')
plt.title('原始医学图像'), plt.axis('off')plt.subplot(222), plt.imshow(binary, cmap='gray')
plt.title('二值化分割'), plt.axis('off')plt.subplot(223), plt.imshow(labeled_img, cmap='nipy_spectral')
plt.title(f'标记的区域 (共{len(props)}个)'), plt.axis('off')# 在原图上标记筛选后的区域
result_img = np.dstack([img, img, img]) # 转换为3通道
for region in filtered_regions:minr, minc, maxr, maxc = region.bboxcv2.rectangle(result_img, (minc, minr), (maxc, maxr), (0, 255, 0), 2)# 计算区域的一些特征centroid = region.centroidarea = region.areaperimeter = region.perimeter# 在图像上标注信息cv2.putText(result_img, f"A:{area:.0f}", (minc, minr-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)plt.subplot(224), plt.imshow(result_img)
plt.title(f'标记的感兴趣区域 (共{len(filtered_regions)}个)'), plt.axis('off')plt.tight_layout()
plt.show()# 输出每个区域的特征
print(f"检测到{len(filtered_regions)}个感兴趣区域:")
for i, region in enumerate(filtered_regions):print(f"区域 {i+1}:")print(f" 面积: {region.area}")print(f" 周长: {region.perimeter}")print(f" 质心: {region.centroid}")print(f" 方向: {region.orientation}")print(f" 偏心率: {region.eccentricity}")print()
7. 特征提取模块设计与实现
特征提取是计算机视觉系统的核心组件,它将图像转换为可用于后续分析和识别的特征向量。本节将介绍如何设计和实现一个模块化的特征提取系统。
7.1 传统特征提取方法
7.1.1 HOG特征提取
方向梯度直方图(HOG)是一种用于目标检测的特征描述符,特别适用于人体检测。
import cv2
import numpy as np
import matplotlib.pyplot as pltdef extract_hog_features(image, cell_size=(8, 8), block_size=(2, 2), nbins=9):"""提取HOG特征参数:image: 输入图像cell_size: HOG单元格大小block_size: 块大小(以单元格为单位)nbins: 方向梯度直方图的bin数量返回:HOG特征向量"""# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 调整图像大小为HOG检测器的标准大小resized = cv2.resize(gray, (64, 128))# 计算HOG特征win_size = (64, 128)block_stride = (cell_size[0] // 2, cell_size[1] // 2)hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, nbins)features = hog.compute(resized)return features# 示例:提取HOG特征并可视化
img = cv2.imread('person.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取HOG特征
hog_features = extract_hog_features(img)
print(f"HOG特征维度: {hog_features.shape}")# 可视化HOG特征(使用OpenCV的内置可视化)
hog = cv2.HOGDescriptor()
img_hog = hog.compute(cv2.resize(img, (64, 128)), visualize=True)[1]plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(122), plt.imshow(img_hog, cmap='gray')
plt.title('HOG特征可视化'), plt.axis('off')plt.tight_layout()
plt.show()
7.1.2 SIFT和ORB特征提取
SIFT(尺度不变特征变换)和ORB(Oriented FAST and Rotated BRIEF)是两种常用的局部特征描述符。
import cv2
import numpy as np
import matplotlib.pyplot as pltdef extract_sift_features(image, n_features=None):"""提取SIFT特征参数:image: 输入图像n_features: 要提取的特征点数量,None表示不限制返回:keypoints: 关键点列表descriptors: 特征描述符"""# 创建SIFT检测器sift = cv2.SIFT_create(nfeatures=n_features)# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 检测关键点并计算描述符keypoints, descriptors = sift.detectAndCompute(gray, None)return keypoints, descriptorsdef extract_orb_features(image, n_features=500):"""提取ORB特征参数:image: 输入图像n_features: 要提取的特征点数量返回:keypoints: 关键点列表descriptors: 特征描述符"""# 创建ORB检测器orb = cv2.ORB_create(nfeatures=n_features)# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 检测关键点并计算描述符keypoints, descriptors = orb.detectAndCompute(gray, None)return keypoints, descriptors# 示例:提取并可视化SIFT和ORB特征
img = cv2.imread('building.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取SIFT特征
sift_keypoints, sift_descriptors = extract_sift_features(img)
print(f"SIFT关键点数量: {len(sift_keypoints)}")
print(f"SIFT描述符维度: {sift_descriptors.shape}")# 提取ORB特征
orb_keypoints, orb_descriptors = extract_orb_features(img)
print(f"ORB关键点数量: {len(orb_keypoints)}")
print(f"ORB描述符维度: {orb_descriptors.shape if orb_descriptors is not None else 'None'}")# 可视化特征点
img_sift = cv2.drawKeypoints(img_rgb, sift_keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img_orb = cv2.drawKeypoints(img_rgb, orb_keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(img_sift)
plt.title(f'SIFT特征点 ({len(sift_keypoints)}个)'), plt.axis('off')plt.subplot(133), plt.imshow(img_orb)
plt.title(f'ORB特征点 ({len(orb_keypoints)}个)'), plt.axis('off')plt.tight_layout()
plt.show()
7.1.3 BRISK和AKAZE特征提取
BRISK(Binary Robust Invariant Scalable Keypoints)和AKAZE(Accelerated-KAZE)是两种高效的特征提取算法。
import cv2
import numpy as np
import matplotlib.pyplot as pltdef extract_brisk_features(image):"""提取BRISK特征"""# 创建BRISK检测器brisk = cv2.BRISK_create()# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 检测关键点并计算描述符keypoints, descriptors = brisk.detectAndCompute(gray, None)return keypoints, descriptorsdef extract_akaze_features(image):"""提取AKAZE特征"""# 创建AKAZE检测器akaze = cv2.AKAZE_create()# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 检测关键点并计算描述符keypoints, descriptors = akaze.detectAndCompute(gray, None)return keypoints, descriptors# 示例:提取并可视化BRISK和AKAZE特征
img = cv2.imread('building.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取BRISK特征
brisk_keypoints, brisk_descriptors = extract_brisk_features(img)
print(f"BRISK关键点数量: {len(brisk_keypoints)}")# 提取AKAZE特征
akaze_keypoints, akaze_descriptors = extract_akaze_features(img)
print(f"AKAZE关键点数量: {len(akaze_keypoints)}")# 可视化特征点
img_brisk = cv2.drawKeypoints(img_rgb, brisk_keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img_akaze = cv2.drawKeypoints(img_rgb, akaze_keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(img_brisk)
plt.title(f'BRISK特征点 ({len(brisk_keypoints)}个)'), plt.axis('off')plt.subplot(133), plt.imshow(img_akaze)
plt.title(f'AKAZE特征点 ({len(akaze_keypoints)}个)'), plt.axis('off')plt.tight_layout()
plt.show()
7.2 颜色特征提取方法
颜色是图像中最直观的特征之一,颜色特征提取方法可以捕捉图像的颜色分布和统计特性。
7.2.1 颜色直方图特征
import cv2
import numpy as np
import matplotlib.pyplot as pltdef extract_color_histogram(image, bins=(8, 8, 8), color_space='RGB'):"""提取颜色直方图特征参数:image: 输入图像bins: 每个颜色通道的bin数量color_space: 颜色空间,可选'RGB', 'HSV', 'Lab'返回:hist_features: 颜色直方图特征向量"""# 转换颜色空间if color_space == 'HSV':image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)elif color_space == 'Lab':image = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)elif color_space == 'RGB':image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 计算颜色直方图hist = cv2.calcHist([image], [0, 1, 2], None, bins, [0, 256, 0, 256, 0, 256])# 归一化直方图cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)# 将直方图转换为特征向量hist_features = hist.flatten()return hist_features# 示例:提取并可视化颜色直方图特征
img = cv2.imread('colorful_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取RGB颜色直方图
rgb_hist_features = extract_color_histogram(img, bins=(8, 8, 8), color_space='RGB')
print(f"RGB颜色直方图特征维度: {rgb_hist_features.shape}")# 提取HSV颜色直方图
hsv_hist_features = extract_color_histogram(img, bins=(8, 8, 8), color_space='HSV')
print(f"HSV颜色直方图特征维度: {hsv_hist_features.shape}")# 可视化单通道直方图
plt.figure(figsize=(15, 10))# RGB通道直方图
for i, color in enumerate(['r', 'g', 'b']):plt.subplot(2, 3, i+1)hist = cv2.calcHist([img_rgb], [i], None, [256], [0, 256])plt.plot(hist, color=color)plt.title(f'{color.upper()}通道直方图')plt.xlim([0, 256])# HSV通道直方图
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
for i, (name, color) in enumerate(zip(['Hue', 'Saturation', 'Value'], ['r', 'g', 'b'])):plt.subplot(2, 3, i+4)hist = cv2.calcHist([img_hsv], [i], None, [256], [0, 256])plt.plot(hist, color=color)plt.title(f'{name}通道直方图')plt.xlim([0, 256])plt.tight_layout()
plt.show()
7.2.2 颜色矩特征
颜色矩是一种紧凑的颜色特征表示,包括均值(一阶矩)、标准差(二阶矩)和偏度(三阶矩)。
def extract_color_moments(image, mask=None):"""提取颜色矩特征(均值、标准差、偏度)参数:image: 输入图像mask: 可选的掩码返回:color_moments: 颜色矩特征向量"""# 确保图像是BGR格式if len(image.shape) < 3:image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)# 转换为HSV颜色空间(通常在HSV空间计算颜色矩更有效)hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)# 初始化特征向量color_moments = []# 对每个通道计算颜色矩for i in range(3):# 提取当前通道channel = hsv[:, :, i]# 计算一阶矩(均值)mean = cv2.mean(channel, mask=mask)[0]# 计算二阶矩(标准差)std_dev = cv2.meanStdDev(channel, mask=mask)[1][0, 0]# 计算三阶矩(偏度)# 首先计算中心化的三次方if mask is None:skewness = np.mean(((channel - mean) / (std_dev + 1e-7)) ** 3)else:masked_channel = channel[mask > 0]skewness = np.mean(((masked_channel - mean) / (std_dev + 1e-7)) ** 3)# 添加到特征向量color_moments.extend([mean, std_dev, skewness])return np.array(color_moments)# 示例:提取并分析颜色矩特征
img = cv2.imread('colorful_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取颜色矩特征
color_moments = extract_color_moments(img)
print("颜色矩特征:")
print(f"H通道: 均值={color_moments[0]:.2f}, 标准差={color_moments[1]:.2f}, 偏度={color_moments[2]:.2f}")
print(f"S通道: 均值={color_moments[3]:.2f}, 标准差={color_moments[4]:.2f}, 偏度={color_moments[5]:.2f}")
print(f"V通道: 均值={color_moments[6]:.2f}, 标准差={color_moments[7]:.2f}, 偏度={color_moments[8]:.2f}")# 可视化原始图像和HSV通道
plt.figure(figsize=(15, 5))plt.subplot(141), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
plt.subplot(142), plt.imshow(img_hsv[:, :, 0], cmap='hsv')
plt.title('H通道'), plt.axis('off')plt.subplot(143), plt.imshow(img_hsv[:, :, 1], cmap='gray')
plt.title('S通道'), plt.axis('off')plt.subplot(144), plt.imshow(img_hsv[:, :, 2], cmap='gray')
plt.title('V通道'), plt.axis('off')plt.tight_layout()
plt.show()
7.2.3 主要颜色提取(基于K-means聚类)
通过K-means聚类可以提取图像中的主要颜色,这对于图像检索和分类非常有用。
def extract_dominant_colors(image, k=5, mask=None):"""使用K-means聚类提取图像中的主要颜色参数:image: 输入图像k: 要提取的颜色数量mask: 可选的掩码返回:colors: 主要颜色列表percentages: 每种颜色的百分比"""# 确保图像是RGB格式if len(image.shape) < 3:image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 将图像重塑为像素列表pixels = image.reshape(-1, 3)# 应用掩码(如果提供)if mask is not None:mask = mask.reshape(-1)pixels = pixels[mask > 0]# 将像素值转换为浮点数pixels = np.float32(pixels)# 定义K-means的终止条件criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)# 应用K-means聚类_, labels, centers = cv2.kmeans(pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)# 将中心点转换为整数centers = np.uint8(centers)# 计算每个聚类的百分比unique_labels, counts = np.unique(labels, return_counts=True)percentages = counts / len(labels) * 100# 按百分比排序sorted_indices = np.argsort(percentages)[::-1]colors = centers[sorted_indices]percentages = percentages[sorted_indices]return colors, percentages# 示例:提取并可视化主要颜色
img = cv2.imread('colorful_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取主要颜色
k = 5
dominant_colors, color_percentages = extract_dominant_colors(img, k=k)# 可视化主要颜色
plt.figure(figsize=(12, 6))plt.subplot(121), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')# 创建颜色条
color_bar = np.zeros((100, 500, 3), dtype=np.uint8)
start_x = 0
width_per_color = 500 // kfor i in range(k):end_x = start_x + int(width_per_color * (color_percentages[i] / 100 * k))color_bar[:, start_x:end_x] = dominant_colors[i]start_x = end_xplt.subplot(122), plt.imshow(color_bar)
plt.title('主要颜色分布'), plt.axis('off')# 打印颜色信息
for i in range(k):print(f"颜色 {i+1}: RGB={dominant_colors[i]}, 百分比={color_percentages[i]:.2f}%")plt.tight_layout()
plt.show()
7.3 纹理特征提取方法
纹理特征描述了图像的空间排列和局部像素变化模式,对于材质识别和场景分类非常重要。
7.3.1 LBP(局部二值模式)特征
from skimage.feature import local_binary_patterndef extract_lbp_features(image, radius=3, n_points=24, method='uniform'):"""提取LBP纹理特征参数:image: 输入图像radius: LBP算子的半径n_points: 邻域采样点数method: LBP方法,可选'default', 'ror', 'uniform', 'var'返回:lbp_hist: LBP直方图特征"""# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 计算LBPlbp = local_binary_pattern(gray, n_points, radius, method=method)# 计算LBP直方图n_bins = int(lbp.max() + 1)hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins), density=True)return hist# 示例:提取并可视化LBP特征
img = cv2.imread('texture_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取LBP特征
radius = 3
n_points = 8 * radius
lbp_features = extract_lbp_features(img, radius=radius, n_points=n_points)
print(f"LBP特征维度: {lbp_features.shape}")# 计算LBP图像用于可视化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
lbp_image = local_binary_pattern(gray, n_points, radius, method='uniform')# 可视化
plt.figure(figsize=(15, 5))plt.subplot(131), plt.imshow(img_rgb)
plt.title('原始图像'), plt.axis('off')plt.subplot(132), plt.imshow(gray, cmap='gray')
plt.title('灰度图像'), plt.axis('off')plt.subplot(133), plt.imshow(lbp_image, cmap='jet')
plt.title('LBP特征图'), plt.axis('off')plt.tight_layout()
plt.show()# 显示LBP直方图
plt.figure(figsize=(10, 4))
plt.bar(range(len(lbp_features)), lbp_features)
plt.title('LBP特征直方图')
plt.xlabel('LBP模式')
plt.ylabel('频率')
plt.show()
7.3.2 Gabor纹理特征
Gabor滤波器是一种用于纹理分析的强大工具,它可以在不同尺度和方向上捕捉图像的纹理信息。
import cv2
import numpy as np
import matplotlib.pyplot as pltdef extract_gabor_features(image, scales=5, orientations=8):"""提取Gabor纹理特征参数:image: 输入图像scales: Gabor滤波器的尺度数量orientations: Gabor滤波器的方向数量返回:gabor_features: Gabor特征向量"""# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 将图像转换为浮点型gray = np.float32(gray)# 初始化特征向量gabor_features = []# 生成Gabor滤波器for scale in range(scales):for orientation in range(orientations):# 创建Gabor滤波器# ksize: 滤波器大小# sigma: 高斯包络的标准差# theta: 滤波器方向# lambda: 正弦波的波长# gamma: 空间宽高比# psi: 相位偏移ksize = 31sigma = 3.0 + scale * 1.0theta = orientation * np.pi / orientationslambd = 10.0gamma = 0.5psi = 0kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, psi, ktype=cv2.CV_32F)# 应用Gabor滤波器filtered = cv2.filter2D(gray, cv2.CV_8UC3, kernel)# 计算滤波后图像的统计特征mean = np.mean(filtered)std = np.std(filtered)# 添加到特征向量gabor_features.extend([mean, std])return np.array(gabor_features)# 示例:提取并可视化Gabor特征
img = cv2.imread('texture_image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 提取Gabor特征
scales = 5
orientations = 8
gabor_features = extract_gabor_features(img, scales=scales, orientations=orientations)
print(f"Gabor特征维度: {gabor_features.shape}")# 可视化Gabor滤波器和滤波结果
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)plt.figure(figsize=(15, 10))# 显示原始图像
plt.subplot(scales+1, orientations+1, 1)
plt.imshow(img_rgb)
plt.title('原始图像')
plt.axis('off')# 生成并显示Gabor滤波器和滤波结果
for scale in range(scales):for orientation in range(orientations):# 创建Gabor滤波器ksize = 31sigma = 3.0 + scale * 1.0theta = orientation * np.pi / orientationslambd = 10.0gamma = 0.5psi = 0kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, psi, ktype=cv2.CV_32F)# 归一化滤波器以便可视化kernel_normalized = kernel / kernel.max()# 应用Gabor滤波器filtered = cv2.filter2D(gray, cv2.CV_8UC3, kernel)# 显示滤波器plt.subplot(scales+1, orientations+1, (scale+1)*(orientations+1) + orientation + 2)plt.imshow(kernel_normalized, cmap='jet')plt.title(f'S{scale+1}O{orientation+1}')plt.axis('off')# 显示滤波结果if scale == 0 and orientation == 0:plt.subplot(scales+1, orientations+1, orientations+2)plt.imshow(filtered, cmap='gray')plt.title('滤波结果示例')plt.axis('off')plt.tight_layout()
plt.show()
7.4 深度学习特征提取方法
深度学习模型,特别是预训练的卷积神经网络(CNN),可以提取高级语义特征,这些特征在各种计算机视觉任务中表现出色。
7.4.1 使用预训练模型提取特征
# 注意:此代码需要安装tensorflow和keras
import tensorflow as tf
from tensorflow.keras.applications import VGG16, ResNet50, MobileNetV2
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.vgg16 import preprocess_input as vgg_preprocess
from tensorflow.keras.applications.resnet50 import preprocess_input as resnet_preprocess
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input as mobilenet_preprocess
import numpy as np
import matplotlib.pyplot as pltdef extract_deep_features(img_path, model_name='vgg16', layer_name=None):"""使用预训练的深度学习模型提取特征参数:img_path: 图像路径model_name: 预训练模型名称,可选'vgg16', 'resnet50', 'mobilenetv2'layer_name: 提取特征的层名称,None表示使用默认层返回:features: 提取的特征向量"""# 加载图像img = image.load_img(img_path, target_size=(224, 224))img_array = image.img_to_array(img)img_array = np.expand_dims(img_array, axis=0)# 根据模型名称选择预训练模型和预处理函数if model_name == 'vgg16':base_model = VGG16(weights='imagenet', include_top=False)preprocess = vgg_preprocessdefault_layer = 'block5_pool'elif model_name == 'resnet50':base_model = ResNet50(weights='imagenet', include_top=False)preprocess = resnet_preprocessdefault_layer = 'conv5_block3_out'elif model_name == 'mobilenetv2':base_model = MobileNetV2(weights='imagenet', include_top=False)preprocess = mobilenet_preprocessdefault_layer = 'out_relu'else:raise ValueError(f"不支持的模型名称: {model_name}")# 预处理图像processed_img = preprocess(img_array)# 选择特征提取层layer_name = layer_name or default_layer# 创建特征提取模型feature_model = tf.keras.Model(inputs=base_model.input,outputs=base_model.get_layer(layer_name).output)# 提取特征features = feature_model.predict(processed_img)# 将特征展平为一维向量features_flat = features.flatten()return features_flat# 示例:提取并比较不同模型的深度特征
img_path = 'cat.jpg'# 提取VGG16特征
vgg16_features = extract_deep_features(img_path, model_name='vgg16')
print(f"VGG16特征维度: {vgg16_features.shape}")# 提取ResNet50特征
resnet50_features = extract_deep_features(img_path, model_name='resnet50')
print(f"ResNet50特征维度: {resnet50_features.shape}")# 提取MobileNetV2特征
mobilenetv2_features = extract_deep_features(img_path, model_name='mobilenetv2')
print(f"MobileNetV2特征维度: {mobilenetv2_features.shape}")# 可视化原始图像
img = image.load_img(img_path)
plt.figure(figsize=(8, 8))
plt.imshow(img)
plt.title('输入图像')
plt.axis('off')
plt.show()# 可视化特征分布
plt.figure(figsize=(15, 5))plt.subplot(131)
plt.hist(vgg16_features, bins=50)
plt.title('VGG16特征分布')
plt.xlabel('特征值')
plt.ylabel('频率')plt.subplot(132)
plt.hist(resnet50_features, bins=50)
plt.title('ResNet50特征分布')
plt.xlabel('特征值')
plt.ylabel('频率')plt.subplot(133)
plt.hist(mobilenetv2_features, bins=50)
plt.title('MobileNetV2特征分布')
plt.xlabel('特征值')
plt.ylabel('频率')plt.tight_layout()
plt.show()
8. 分类与识别模块设计与实现
在完成特征提取后,下一步是使用这些特征进行图像分类和识别。本节将介绍如何设计和实现一个模块化的分类与识别系统。
8.1 传统机器学习分类方法
8.1.1 SVM分类器
支持向量机(SVM)是一种强大的分类算法,特别适用于高维特征空间。
import cv2
import numpy as np
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
import pickledef train_svm_classifier(features, labels, kernel='rbf', C=1.0, gamma='scale'):"""训练SVM分类器参数:features: 特征矩阵,每行是一个样本的特征向量labels: 标签向量kernel: 核函数类型,可选'linear', 'poly', 'rbf', 'sigmoid'C: 正则化参数gamma: 'rbf', 'poly' 和 'sigmoid' 核函数的参数返回:classifier: 训练好的SVM分类器"""# 创建SVM分类器classifier = svm.SVC(kernel=kernel, C=C, gamma=gamma, probability=True)# 训练分类器classifier.fit(features, labels)return classifierdef evaluate_classifier(classifier, X_test, y_test):"""评估分类器性能参数:classifier: 训练好的分类器X_test: 测试特征y_test: 测试标签返回:accuracy: 准确率report: 分类报告"""# 预测y_pred = classifier.predict(X_test)# 计算准确率accuracy = accuracy_score(y_test, y_pred)# 生成分类报告report = classification_report(y_test, y_pred)return accuracy, reportdef save_classifier(classifier, filename):"""保存分类器到文件"""with open(filename, 'wb') as f:pickle.dump(classifier, f)def load_classifier(filename):"""从文件加载分类器"""with open(filename, 'rb') as f:classifier = pickle.load(f)return classifier# 示例:使用HOG特征和SVM进行图像分类
def extract_hog_features(image):"""提取HOG特征"""# 调整图像大小resized = cv2.resize(image, (64, 128))# 创建HOG描述符hog = cv2.HOGDescriptor()# 计算HOG特征features = hog.compute(resized)return features.flatten()# 假设我们有一个图像数据集
# 这里只是示例代码,实际应用中需要替换为真实数据
def load_dataset(dataset_path):"""加载图像数据集"""# 这里应该是加载真实数据集的代码# 返回图像列表和对应的标签# 这里使用随机生成的数据作为示例np.random.seed(42)num_samples = 100images = [np.random.randint(0, 256, (128, 64, 3), dtype=np.uint8) for _ in range(num_samples)]labels = np.random.randint(0, 2, num_samples) # 二分类问题return images, labels# 加载数据集
dataset_path = "path/to/dataset"
images, labels = load_dataset(dataset_path)# 提取特征
features = [extract_hog_features(img) for img in images]
features = np.array(features)# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.2, random_state=42)# 训练SVM分类器
svm_classifier = train_svm_classifier(X_train, y_train, kernel='rbf', C=10.0)# 评估分类器
accuracy, report = evaluate_classifier(svm_classifier, X_test, y_test)
print(f"SVM分类器准确率: {accuracy:.4f}")
print("分类报告:")
print(report)# 保存分类器
save_classifier(svm_classifier, "svm_classifier.pkl")# 可视化决策边界(仅适用于二维特征)
def visualize_decision_boundary(classifier, X, y):"""可视化二维特征空间中的决策边界"""# 使用PCA将特征降至2维(如果原始特征维度大于2)if X.shape[1] > 2:from sklearn.decomposition import PCApca = PCA(n_components=2)X_2d = pca.fit_transform(X)else:X_2d = X# 创建网格点h = 0.02 # 网格步长x_min, x_max = X_2d[:, 0].min() - 1, X_2d[:, 0].max() + 1y_min, y_max = X_2d[:, 1].min() - 1, X_2d[:, 1].max() + 1xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))# 在网格点上进行预测Z = classifier.predict(np.c_[xx.ravel(), yy.ravel()])Z = Z.reshape(xx.shape)# 绘制决策边界和样本点plt.figure(figsize=(10, 8))plt.contourf(xx, yy, Z, alpha=0.8)plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y, edgecolors='k', marker='o')plt.xlabel('特征1')plt.ylabel('特征2')plt.title('SVM决策边界')plt.colorbar()plt.show()# 如果特征维度过高,可以使用PCA降维后再可视化
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_2d = pca.fit_transform(features)# 在降维后的特征空间训练一个新的SVM
svm_2d = train_svm_classifier(X_2d, labels, kernel='rbf')# 可视化决策边界
visualize_decision_boundary(svm_2d, X_2d, labels)
8.1.2 KNN分类器
K近邻(KNN)是一种简单但有效的分类算法,基于样本在特征空间中的距离进行分类。
from sklearn.neighbors import KNeighborsClassifierdef train_knn_classifier(features, labels, n_neighbors=5, weights='uniform', algorithm='auto'):"""训练KNN分类器参数:features: 特征矩阵,每行是一个样本的特征向量labels: 标签向量n_neighbors: 近邻数量weights: 权重类型,可选'uniform', 'distance'algorithm: 算法类型,可选'auto', 'ball_tree', 'kd_tree', 'brute'返回:classifier: 训练好的KNN分类器"""# 创建KNN分类器classifier = KNeighborsClassifier(n_neighbors=n_neighbors,weights=weights,algorithm=algorithm)# 训练分类器classifier.fit(features, labels)return classifier# 使用前面提取的特征训练KNN分类器
knn_classifier = train_knn_classifier(X_train, y_train, n_neighbors=5, weights='distance')# 评估KNN分类器
accuracy, report = evaluate_classifier(knn_classifier, X_test, y_test)
print(f"KNN分类器准确率: {accuracy:.4f}")
print("分类报告:")
print(report)# 保存KNN分类器
save_classifier(knn_classifier, "knn_classifier.pkl")# 可视化KNN决策边界
visualize_decision_boundary(train_knn_classifier(X_2d, labels, n_neighbors=5), X_2d, labels)
8.1.3 随机森林分类器
随机森林是一种集成学习方法,通过构建多个决策树并合并它们的预测结果来提高分类性能。
from sklearn.ensemble import RandomForestClassifierdef train_random_forest_classifier(features, labels, n_estimators=100, max_depth=None, min_samples_split=2):"""训练随机森林分类器参数:features: 特征矩阵,每行是一个样本的特征向量labels: 标签向量n_estimators: 决策树数量max_depth: 树的最大深度min_samples_split: 分裂内部节点所需的最小样本数返回:classifier: 训练好的随机森林分类器"""# 创建随机森林分类器classifier = RandomForestClassifier(n_estimators=n_estimators,max_depth=max_depth,min_samples_split=min_samples_split,random_state=42)# 训练分类器classifier.fit(features, labels)return classifier# 使用前面提取的特征训练随机森林分类器
rf_classifier = train_random_forest_classifier(X_train, y_train, n_estimators=100)# 评估随机森林分类器
accuracy, report = evaluate_classifier(rf_classifier, X_test, y_test)
print(f"随机森林分类器准确率: {accuracy:.4f}")
print("分类报告:")
print(report)# 保存随机森林分类器
save_classifier(rf_classifier, "rf_classifier.pkl")# 可视化特征重要性
def visualize_feature_importance(classifier, feature_names=None):"""可视化随机森林的特征重要性"""importances = classifier.feature_importances_indices = np.argsort(importances)[::-1]plt.figure(figsize=(12, 6))plt.title('特征重要性')plt.bar(range(len(importances)), importances[indices], align='center')if feature_names is not None:plt.xticks(range(len(importances)), [feature_names[i] for i in indices], rotation=90)else:plt.xticks(range(len(importances)), indices)plt.tight_layout()plt.show()# 假设我们有特征名称
feature_names = [f"特征{i+1}" for i in range(X_train.shape[1])]# 可视化特征重要性
visualize_feature_importance(rf_classifier, feature_names)
8.2 深度学习分类方法
深度学习,特别是卷积神经网络(CNN),已经在图像分类任务中取得了突破性的进展。
8.2.1 使用预训练模型进行分类
# 注意:此代码需要安装tensorflow和keras
import tensorflow as tf
from tensorflow.keras.applications import VGG16, ResNet50, MobileNetV2, EfficientNetB0
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
import matplotlib.pyplot as pltdef classify_image_with_pretrained_model(img_path, model_name='vgg16'):"""使用预训练的深度学习模型对图像进行分类参数:img_path: 图像路径model_name: 预训练模型名称,可选'vgg16', 'resnet50', 'mobilenetv2', 'efficientnetb0'返回:predictions: 预测结果(类别和概率)"""# 加载图像img = image.load_img(img_path, target_size=(224, 224))img_array = image.img_to_array(img)img_array = np.expand_dims(img_array, axis=0)# 根据模型名称选择预训练模型if model_name == 'vgg16':model = VGG16(weights='imagenet')processed_img = preprocess_input(img_array)elif model_name == 'resnet50':model = ResNet50(weights='imagenet')processed_img = tf.keras.applications.resnet50.preprocess_input(img_array)elif model_name == 'mobilenetv2':model = MobileNetV2(weights='imagenet')processed_img = tf.keras.applications.mobilenet_v2.preprocess_input(img_array)elif model_name == 'efficientnetb0':model = EfficientNetB0(weights='imagenet')processed_img = tf.keras.applications.efficientnet.preprocess_input(img_array)else:raise ValueError(f"不支持的模型名称: {model_name}")# 预测predictions = model.predict(processed_img)# 解码预测结果decoded_predictions = decode_predictions(predictions, top=5)[0]return decoded_predictions# 示例:使用不同的预训练模型对图像进行分类
img_path = 'elephant.jpg'# 使用VGG16分类
vgg16_predictions = classify_image_with_pretrained_model(img_path, model_name='vgg16')
print("VGG16预测结果:")
for i, (imagenet_id, label, score) in enumerate(vgg16_predictions):print(f"{i+1}: {label} ({score:.4f})")# 使用ResNet50分类
resnet50_predictions = classify_image_with_pretrained_model(img_path, model_name='resnet50')
print("\nResNet50预测结果:")
for i, (imagenet_id, label, score) in enumerate(resnet50_predictions):print(f"{i+1}: {label} ({score:.4f})")# 使用MobileNetV2分类
mobilenetv2_predictions = classify_image_with_pretrained_model(img_path, model_name='mobilenetv2')
print("\nMobileNetV2预测结果:")
for i, (imagenet_id, label, score) in enumerate(mobilenetv2_predictions):print(f"{i+1}: {label} ({score:.4f})")# 可视化结果
img = image.load_img(img_path)
plt.figure(figsize=(10, 10))
plt.imshow(img)
plt.axis('off')
plt.title(f"预测结果: {vgg16_predictions[0][1]} ({vgg16_predictions[0][2]:.4f})")
plt.show()# 比较不同模型的预测结果
models = ['VGG16', 'ResNet50', 'MobileNetV2']
predictions = [vgg16_predictions, resnet50_predictions, mobilenetv2_predictions]plt.figure(figsize=(15, 8))
for i, (model_name, model_predictions) in enumerate(zip(models, predictions)):plt.subplot(1, 3, i+1)# 提取类别和概率labels = [pred[1] for pred in model_predictions]scores = [pred[2] for pred in model_predictions]# 绘制条形图y_pos = np.arange(len(labels))plt.barh(y_pos, scores, align='center')plt.yticks(y_pos, labels)plt.xlabel('概率')plt.title(f'{model_name}预测结果')plt.tight_layout()
plt.show()
8.2.2 微调预训练模型
微调是一种迁移学习技术,它利用预训练模型的知识来解决特定的分类任务。
import tensorflow as tf
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam
import matplotlib.pyplot as pltdef create_fine_tuned_model(base_model_name='mobilenetv2', num_classes=10, input_shape=(224, 224, 3)):"""创建一个基于预训练模型的微调模型参数:base_model_name: 预训练模型名称num_classes: 目标类别数量input_shape: 输入图像形状返回:model: 微调模型"""# 加载预训练模型if base_model_name == 'mobilenetv2':base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=input_shape)else:raise ValueError(f"不支持的模型名称: {base_model_name}")# 冻结基础模型的层for layer in base_model.layers:layer.trainable = False# 添加自定义分类头x = base_model.outputx = GlobalAveragePooling2D()(x)x = Dense(1024, activation='relu')(x)predictions = Dense(num_classes, activation='softmax')(x)# 创建新模型model = Model(inputs=base_model.input, outputs=predictions)return model# 示例:微调MobileNetV2模型用于花卉分类
# 假设我们有一个花卉数据集,包含5个类别# 数据增强
train_datagen = ImageDataGenerator(preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,rotation_range=20,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,fill_mode='nearest'
)test_datagen = ImageDataGenerator(preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input
)# 加载数据
# 注意:这里需要替换为实际的数据路径
train_dir = 'path/to/train'
validation_dir = 'path/to/validation'train_generator = train_datagen.flow_from_directory(train_dir,target_size=(224, 224),batch_size=32,class_mode='categorical'
)validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(224, 224),batch_size=32,class_mode='categorical'
)# 创建微调模型
num_classes = len(train_generator.class_indices)
model = create_fine_tuned_model(base_model_name='mobilenetv2', num_classes=num_classes)# 编译模型
model.compile(optimizer=Adam(learning_rate=0.0001),loss='categorical_crossentropy',metrics=['accuracy']
)# 训练模型
# 注意:实际训练时需要更多的epochs
history = model.fit(train_generator,steps_per_epoch=train_generator.samples // train_generator.batch_size,epochs=10,validation_data=validation_generator,validation_steps=validation_generator.samples // validation_generator.batch_size
)# 保存模型
model.save('fine_tuned_mobilenetv2.h5')# 可视化训练过程
def plot_training_history(history):"""可视化训练历史"""acc = history.history['accuracy']val_acc = history.history['val_accuracy']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(1, len(acc) + 1)plt.figure(figsize=(12, 5))plt.subplot(1, 2, 1)plt.plot(epochs, acc, 'b-', label='训练准确率')plt.plot(epochs, val_acc, 'r-', label='验证准确率')plt.title('训练和验证准确率')plt.xlabel('Epochs')plt.ylabel('准确率')plt.legend()plt.subplot(1, 2, 2)plt.plot(epochs, loss, 'b-', label='训练损失')plt.plot(epochs, val_loss, 'r-', label='验证损失')plt.title('训练和验证损失')plt.xlabel('Epochs')plt.ylabel('损失')plt.legend()plt.tight_layout()plt.show()# 可视化训练历史
plot_training_history(history)
8.3 目标检测方法
目标检测是计算机视觉中的一项关键任务,它不仅需要识别图像中的对象,还需要定位它们的位置。
8.3.1 基于YOLOv5的目标检测
YOLO (You Only Look Once) 是一种流行的实时目标检测系统,YOLOv5是其最新版本之一。
# 注意:此代码需要安装PyTorch和YOLOv5
import torch
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Imagedef detect_objects_with_yolov5(img_path, model_name='yolov5s', conf_threshold=0.25, iou_threshold=0.45):"""使用YOLOv5进行目标检测参数:img_path: 图像路径model_name: YOLOv5模型名称,可选'yolov5s', 'yolov5m', 'yolov5l', 'yolov5x'conf_threshold: 置信度阈值iou_threshold: IoU阈值返回:results: 检测结果"""# 加载YOLOv5模型model = torch.hub.load('ultralytics/yolov5', model_name)# 设置置信度和IoU阈值model.conf = conf_thresholdmodel.iou = iou_threshold# 进行检测results = model(img_path)return results# 示例:使用YOLOv5进行目标检测
img_path = 'street_scene.jpg'# 检测目标
results = detect_objects_with_yolov5(img_path, model_name='yolov5s')# 显示结果
results.show()# 获取检测结果
detections = results.pandas().xyxy[0] # 边界框坐标、置信度和类别
print(detections)# 可视化检测结果
img = cv2.imread(img_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)plt.figure(figsize=(12, 8))
plt.imshow(img_rgb)
plt.axis('off')
plt.title('原始图像')
plt.show()# 在图像上绘制检测结果
result_img = img_rgb.copy()
for i, detection in detections.iterrows():# 获取边界框坐标x1, y1, x2, y2 = int(detection['xmin']), int(detection['ymin']), int(detection['xmax']), int(detection['ymax'])# 获取类别和置信度label = detection['name']confidence = detection['confidence']# 绘制边界框cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)# 绘制标签text = f"{label}: {confidence:.2f}"cv2.putText(result_img, text, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)plt.figure(figsize=(12, 8))
plt.imshow(result_img)
plt.axis('off')
plt.title('YOLOv5检测结果')
plt.show()# 统计检测到的各类别数量
class_counts = detections['name'].value_counts()
print("检测到的各类别数量:")
print(class_counts)# 可视化类别分布
plt.figure(figsize=(10, 6))
class_counts.plot(kind='bar')
plt.title('检测到的各类别数量')
plt.xlabel('类别')
plt.ylabel('数量')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
8.4 综合分类和识别方法
在实际应用中,我们通常需要将特征提取和分类方法结合起来,形成一个完整的图像识别系统。
8.4.1 特征提取与分类集成
import cv2
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
import time
import pickleclass ImageClassificationSystem:"""综合图像分类系统,集成特征提取和分类功能"""def __init__(self, feature_extractors=None, classifier_type='svm', classifier_params=None):"""初始化图像分类系统参数:feature_extractors: 特征提取器列表,每个元素是(提取器函数, 权重)classifier_type: 分类器类型,可选'svm', 'rf', 'knn'classifier_params: 分类器参数字典"""self.feature_extractors = feature_extractors or []self.classifier_type = classifier_typeself.classifier_params = classifier_params or {}self.classifier = Noneself.scaler = StandardScaler()def add_feature_extractor(self, extractor_func, weight=1.0):"""添加特征提取器"""self.feature_extractors.append((extractor_func, weight))def extract_features(self, image):"""提取图像特征"""features = []weights = []for extractor_func, weight in self.feature_extractors:start_time = time.time()feature = extractor_func(image)end_time = time.time()print(f"提取器 {extractor_func.__name__} 耗时: {end_time - start_time:.4f}秒")print(f"特征维度: {feature.shape}")features.append(feature)weights.append(weight)# 如果只有一个特征提取器,直接返回特征if len(features) == 1:return features[0]# 否则,进行特征融合# 首先确保所有特征向量的长度相同max_length = max(len(f) for f in features)normalized_features = []for feature, weight in zip(features, weights):# 如果特征长度不足,进行零填充if len(feature) < max_length:padded = np.zeros(max_length)padded[:len(feature)] = featurenormalized_features.append(padded * weight)else:normalized_features.append(feature[:max_length] * weight)# 融合特征fused_feature = np.mean(normalized_features, axis=0)return fused_featuredef train(self, images, labels):"""训练分类器"""# 提取特征features = [self.extract_features(img) for img in images]features = np.array(features)# 标准化特征self.scaler.fit(features)features_scaled = self.scaler.transform(features)# 创建分类器if self.classifier_type == 'svm':self.classifier = SVC(C=self.classifier_params.get('C', 1.0),kernel=self.classifier_params.get('kernel', 'rbf'),gamma=self.classifier_params.get('gamma', 'scale'),probability=True)elif self.classifier_type == 'rf':self.classifier = RandomForestClassifier(n_estimators=self.classifier_params.get('n_estimators', 100),max_depth=self.classifier_params.get('max_depth', None),min_samples_split=self.classifier_params.get('min_samples_split', 2),random_state=42)elif self.classifier_type == 'knn':from sklearn.neighbors import KNeighborsClassifierself.classifier = KNeighborsClassifier(n_neighbors=self.classifier_params.get('n_neighbors', 5),weights=self.classifier_params.get('weights', 'uniform'),algorithm=self.classifier_params.get('algorithm', 'auto'))else:raise ValueError(f"不支持的分类器类型: {self.classifier_type}")# 训练分类器self.classifier.fit(features_scaled, labels)# 返回训练准确率predictions = self.classifier.predict(features_scaled)accuracy = accuracy_score(labels, predictions)return accuracydef predict(self, image):"""预测图像类别"""# 提取特征feature = self.extract_features(image)feature = np.array([feature])# 标准化特征feature_scaled = self.scaler.transform(feature)# 预测prediction = self.classifier.predict(feature_scaled)[0]probabilities = self.classifier.predict_proba(feature_scaled)[0]return prediction, probabilitiesdef evaluate(self, test_images, test_labels):"""评估分类器性能"""# 提取特征features = [self.extract_features(img) for img in test_images]features = np.array(features)# 标准化特征features_scaled = self.scaler.transform(features)# 预测predictions = self.classifier.predict(features_scaled)# 计算准确率accuracy = accuracy_score(test_labels, predictions)# 生成分类报告report = classification_report(test_labels, predictions)return accuracy, reportdef save(self, filename):"""保存模型"""model_data = {'classifier': self.classifier,'scaler': self.scaler,'classifier_type': self.classifier_type,'classifier_params': self.classifier_params}with open(filename, 'wb') as f:pickle.dump(model_data, f)@classmethoddef load(cls, filename, feature_extractors=None):"""加载模型"""with open(filename, 'rb') as f:model_data = pickle.load(f)# 创建新实例instance = cls(feature_extractors=feature_extractors,classifier_type=model_data['classifier_type'],classifier_params=model_data['classifier_params'])# 加载分类器和标准化器instance.classifier = model_data['classifier']instance.scaler = model_data['scaler']return instance# 示例:创建一个综合图像分类系统# 定义特征提取函数
def extract_color_histogram(image, bins=(8, 8, 8)):"""提取颜色直方图特征"""# 确保图像是BGR格式if len(image.shape) < 3:image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)# 计算颜色直方图hist = cv2.calcHist([image], [0, 1, 2], None, bins, [0, 256, 0, 256, 0, 256])# 归一化直方图cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)# 将直方图转换为特征向量hist_features = hist.flatten()return hist_featuresdef extract_hog_features(image):"""提取HOG特征"""# 确保图像是灰度图if len(image.shape) > 2:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)else:gray = image# 调整图像大小resized = cv2.resize(gray, (64, 128))# 计算HOG特征hog = cv2.HOGDescriptor()features = hog.compute(resized)return features.flatten()# 创建图像分类系统
classification_system = ImageClassificationSystem(classifier_type='svm', classifier_params={'C': 10.0, 'kernel': 'rbf'})# 添加特征提取器
classification_system.add_feature_extractor(extract_color_histogram, weight=0.7)
classification_system.add_feature_extractor(extract_hog_features, weight=1.0)# 假设我们有一个图像数据集
# 这里只是示例代码,实际应用中需要替换为真实数据
def load_dataset(dataset_path):"""加载图像数据集"""# 这里应该是加载真实数据集的代码# 返回图像列表和对应的标签# 这里使用随机生成的数据作为示例np.random.seed(42)num_samples = 100images = [np.random.randint(0, 256, (128, 64, 3), dtype=np.uint8) for _ in range(num_samples)]labels = np.random.randint(0, 2, num_samples) # 二分类问题return images, labels# 加载数据集
dataset_path = "path/to/dataset"
images, labels = load_dataset(dataset_path)# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(images, labels, test_size=0.2, random_state=42)# 训练分类系统
print("开始训练分类系统...")
train_accuracy = classification_system.train(X_train, y_train)
print(f"训练准确率: {train_accuracy:.4f}")# 评估分类系统
print("评估分类系统...")
test_accuracy, test_report = classification_system.evaluate(X_test, y_test)
print(f"测试准确率: {test_accuracy:.4f}")
print("分类报告:")
print(test_report)# 保存分类系统
classification_system.save("image_classification_system.pkl")# 对单个图像进行预测
test_image = X_test[0]
true_label = y_test[0]
predicted_label, probabilities = classification_system.predict(test_image)print(f"真实标签: {true_label}")
print(f"预测标签: {predicted_label}")
print(f"预测概率: {probabilities}")# 可视化预测结果
plt.figure(figsize=(8, 8))
plt.imshow(cv2.cvtColor(test_image, cv2.COLOR_BGR2RGB))
plt.title(f"真实标签: {true_label}, 预测标签: {predicted_label} (置信度: {probabilities[predicted_label]:.4f})")
plt.axis('off')
plt.show()
9. 总结与展望
9.1 技术总结
在本系列教程中,我们全面介绍了Python图像处理与计算机视觉的各个方面,从基础的图像操作到高级的深度学习方法。主要内容包括:
-
OpenCV基础:环境搭建、图像读写、像素操作、几何变换等基本操作。
-
图像处理基础:颜色空间转换、图像滤波、边缘检测、图像增强与修复等技术。
-
特征提取与描述:传统特征提取方法(HOG、SIFT、ORB等)、颜色特征、纹理特征以及深度学习特征。
-
图像分割:基于阈值的分割、基于边缘的分割、基于区域的分割以及深度学习分割方法。
-
目标检测与识别:传统机器学习方法(Haar级联分类器)和深度学习方法(YOLO、SSD等)。
-
分类与识别系统:传统机器学习分类器(SVM、KNN、随机森林)和深度学习分类器,以及如何构建完整的图像识别系统。
-
实战项目:文档扫描与矫正、人脸检测与识别、车牌识别、医学图像分析等实际应用案例。
9.2 未来发展趋势
计算机视觉领域正在快速发展,以下是一些值得关注的趋势:
-
自监督学习:减少对标注数据的依赖,通过自监督学习方法从大量未标注数据中学习有用的表示。
-
多模态学习:结合图像、文本、音频等多种模态的信息,提高模型的理解能力和泛化能力。
-
轻量级模型:为移动设备和边缘计算设计的高效、低功耗模型,如MobileNet系列、EfficientNet系列等。
-
3D视觉:从2D图像理解向3D场景理解发展,包括3D重建、深度估计、点云处理等。
-
视频理解:从静态图像分析向动态视频理解发展,包括动作识别、视频分割、视频生成等。
-
生成模型:基于GAN、VAE、Diffusion Models等的图像生成和编辑技术。
-
跨域适应:解决模型在不同域之间的迁移问题,提高模型的泛化能力。
9.3 学习建议
对于想要深入学习计算机视觉的读者,以下是一些建议:
-
打好基础:掌握图像处理的基本原理和技术,理解像素、颜色空间、滤波等概念。
-
学习数学:线性代数、概率统计、微积分是计算机视觉的数学基础。
-
编程实践:通过实际项目巩固所学知识,可以从简单的图像处理任务开始,逐步过渡到复杂的计算机视觉应用。
-
关注前沿:定期阅读顶级会议(CVPR、ICCV、ECCV等)的论文,了解最新的研究进展。
-
参与社区:加入开源社区,参与项目开发,与其他开发者交流学习。
-
跨学科学习:计算机视觉与机器学习、深度学习、图形学等领域密切相关,拓宽知识面有助于解决复杂问题。