机器视觉基础实验:图像处理与特征分析
一、实验目的
通过本实验,掌握视频流接入、图像预处理、降噪与边缘增强、纹理特征提取和光照变换等核心图像处理技术,为后续智能感知算法开发奠定基础。
1.1 知识目标
- 理解视频流的基本概念(帧率、分辨率、编码格式)及其在智能感知中的应用
- 掌握图像噪声的类型(高斯噪声、椒盐噪声)及常用滤波方法的原理(均值、高斯、中值、双边滤波)
- 理解图像边缘的物理意义及边缘检测算子(Sobel、Laplacian、Canny)的数学原理
- 掌握纹理特征的概念及局部二值模式(LBP)、灰度共生矩阵(GLCM)的统计原理
- 理解光照变换(直方图均衡化、CLAHE、伽马校正)对图像对比度和亮度的影响机制
1.2 技能目标
- 能够使用OpenCV完成摄像头视频流捕获、视频文件读取及视频分帧与图像提取
- 能够对图像进行灰度转换、尺寸调整、保存等基本操作
- 能够模拟图像噪声并应用均值滤波、高斯滤波、中值滤波、双边滤波进行降噪
- 能够使用Sobel、Laplacian、Canny算子完成边缘检测,并实现边缘增强
- 能够提取图像的LBP特征直方图和GLCM统计特征(对比度、能量、同质性等)
- 能够应用直方图均衡化、CLAHE和伽马校正进行光照归一化处理
- 能够将光照变换与纹理特征提取结合,完成综合性的图像分析任务
1.3 素养目标
- 培养参数调优与结果对比分析的实验习惯
- 建立图像预处理与特征提取在智能感知系统中的重要地位认知
- 提升实验记录、可视化展示和报告撰写的规范能力
二、实验内容
本实验包含四大模块:
模块一:视频流接入与图像基本操作
- 安装OpenCV库并验证环境
- 从摄像头实时捕获视频流并显示
- 从本地视频文件读取并逐帧处理
- 按帧间隔或时间间隔提取视频帧并保存为图片
- 图像的读取、灰度转换、尺寸调整、保存等基本操作
模块二:图像降噪与边缘增强
- 人为添加高斯噪声和椒盐噪声,观察噪声影响
- 使用均值滤波、高斯滤波、中值滤波、双边滤波进行降噪处理,对比效果
- 使用Sobel、Laplacian、Canny算子检测图像边缘
- 将边缘信息叠加到原图实现边缘增强
- 整合为实时视频流降噪与边缘检测演示程序
模块三:图像纹理特征提取
- 理解纹理特征的概念及LBP和GLCM的基本原理
- 使用
skimage库计算图像的LBP编码图及归一化直方图 - 使用均匀模式LBP(Uniform LBP)降维并比较
- 构建灰度共生矩阵(GLCM)并提取对比度、能量、同质性、相关性等统计特征
- 对比不同纹理图像(如砖墙、草地、砾石)的LBP直方图和GLCM特征差异
模块四:图像光照变换
- 计算并绘制灰度图像的直方图,分析亮度分布
- 应用全局直方图均衡化(HE)提升对比度
- 应用自适应直方图均衡化(CLAHE),调整
clipLimit参数观察效果 - 应用伽马校正(Gamma Correction)模拟不同光照条件
- 分析光照变换对LBP纹理特征的影响(鲁棒性测试)
三、实验步骤
3.1 环境准备与OpenCV安装
-
安装OpenCV及相关库
打开Anaconda Prompt,激活前序实验创建的
dl_env环境:bash
conda activate dl_env pip install opencv-python opencv-contrib-python pip install numpy matplotlib scikit-image scipy -
验证安装
创建测试文件
test_opencv.py:python
import cv2 import numpy as np import skimage print("OpenCV版本:", cv2.__version__) print("scikit-image版本:", skimage.__version__) print("NumPy版本:", np.__version__)运行无误即表示环境准备完成。
3.2 视频流接入与图像基本操作
步骤1:摄像头视频流捕获
创建video_capture.py,实现从默认摄像头读取并显示实时画面:
python
import cv2
cap = cv2.VideoCapture(0) # 0表示默认摄像头
if not cap.isOpened():
print("无法打开摄像头")
exit()
# 设置分辨率
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
print("按 'q' 键退出")
while True:
ret, frame = cap.read()
if not ret:
break
cv2.imshow("Camera Stream", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
步骤2:从视频文件读取与分帧提取
创建video_to_frames.py,实现对本地视频文件的读取和按帧保存:
python
import cv2
import os
def extract_frames(video_path, output_dir, frame_interval=30):
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print("无法打开视频")
return
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap.get(cv2.CAP_PROP_FPS)
print(f"总帧数: {total_frames}, 帧率: {fps:.2f}")
count = 0
saved = 0
while True:
ret, frame = cap.read()
if not ret:
break
if count % frame_interval == 0:
filename = os.path.join(output_dir, f"frame_{saved:06d}.jpg")
cv2.imwrite(filename, frame)
saved += 1
if saved % 10 == 0:
print(f"已保存 {saved} 张")
count += 1
cap.release()
print(f"完成,共保存 {saved} 张图片")
if __name__ == "__main__":
extract_frames("./test_video.mp4", "./extracted_frames", frame_interval=30)
步骤3:图像基本操作
创建image_basic_ops.py,演示图像的读取、灰度转换、尺寸调整与保存:
python
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("./test_image.jpg")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_resized = cv2.resize(img, (256, 256))
plt.figure(figsize=(12,4))
plt.subplot(1,3,1); plt.imshow(img_rgb); plt.title("原始"); plt.axis('off')
plt.subplot(1,3,2); plt.imshow(img_gray, cmap='gray'); plt.title("灰度"); plt.axis('off')
plt.subplot(1,3,3); plt.imshow(cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)); plt.title("调整尺寸"); plt.axis('off')
plt.show()
3.3 图像降噪与边缘增强
步骤4:噪声模拟与滤波对比
创建denoise_demo.py,添加噪声并应用四种滤波方法:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
def add_gaussian_noise(img, sigma=25):
noise = np.random.normal(0, sigma, img.shape).astype(np.uint8)
return cv2.add(img, noise)
def add_salt_pepper_noise(img, prob=0.02):
noisy = img.copy()
salt = np.random.random(img.shape[:2]) < prob/2
pepper = np.random.random(img.shape[:2]) < prob/2
noisy[salt] = 255
noisy[pepper] = 0
return noisy
img = cv2.imread("./test_image.jpg", cv2.IMREAD_GRAYSCALE)
img_gauss = add_gaussian_noise(img, 30)
img_sp = add_salt_pepper_noise(img, 0.03)
# 滤波
blur_mean = cv2.blur(img_gauss, (5,5))
blur_gaussian = cv2.GaussianBlur(img_gauss, (5,5), 1.5)
blur_median = cv2.medianBlur(img_sp, 5)
blur_bilateral = cv2.bilateralFilter(img_gauss, 9, 75, 75)
# 显示对比
titles = ['原始', '高斯噪声', '均值滤波', '高斯滤波', '椒盐噪声', '中值滤波', '双边滤波']
images = [img, img_gauss, blur_mean, blur_gaussian, img_sp, blur_median, blur_bilateral]
plt.figure(figsize=(15,10))
for i in range(7):
plt.subplot(2,4,i+1); plt.imshow(images[i], cmap='gray'); plt.title(titles[i]); plt.axis('off')
plt.tight_layout()
plt.savefig('./results/denoise_comparison.png', dpi=150)
plt.show()
步骤5:边缘检测与增强
创建edge_detection.py:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("./test_image.jpg", cv2.IMREAD_GRAYSCALE)
# Sobel
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel_mag = np.uint8(np.sqrt(sobelx**2 + sobely**2))
# Laplacian
laplacian = np.uint8(np.abs(cv2.Laplacian(img, cv2.CV_64F, ksize=3)))
# Canny (先高斯滤波)
img_blur = cv2.GaussianBlur(img, (5,5), 1.5)
canny = cv2.Canny(img_blur, 50, 150)
# 边缘增强(叠加Laplacian)
enhanced = cv2.addWeighted(img, 1.0, laplacian, 0.5, 0)
plt.figure(figsize=(15,10))
titles = ['原图', 'Sobel X', 'Sobel Y', 'Sobel幅值', 'Laplacian', 'Canny', '边缘增强']
images = [img, np.abs(sobelx), np.abs(sobely), sobel_mag, laplacian, canny, enhanced]
for i in range(7):
plt.subplot(2,4,i+1); plt.imshow(images[i], cmap='gray'); plt.title(titles[i]); plt.axis('off')
plt.tight_layout()
plt.savefig('./results/edge_detection.png', dpi=150)
plt.show()
步骤6:实时视频流处理综合程序
创建realtime_process.py,整合降噪和边缘检测,按键盘切换模式:
python
import cv2
cap = cv2.VideoCapture(0)
denoise = True
edge = False
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
processed = gray
if denoise:
processed = cv2.GaussianBlur(processed, (5,5), 1.5)
if edge:
edges = cv2.Canny(processed, 50, 150)
processed = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
else:
processed = cv2.cvtColor(processed, cv2.COLOR_GRAY2BGR)
cv2.putText(processed, f"Denoise:{denoise} Edge:{edge}", (10,30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
cv2.imshow("Real-time", processed)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('d'):
denoise = not denoise
elif key == ord('e'):
edge = not edge
cap.release()
cv2.destroyAllWindows()
3.4 纹理特征提取
步骤7:LBP特征提取与直方图对比
创建texture_lbp.py,使用skimage加载纹理图片并提取LBP:
python
import numpy as np
import matplotlib.pyplot as plt
from skimage.feature import local_binary_pattern
from skimage import data
# 加载三种纹理
brick = data.load('brick')
grass = data.load('grass')
gravel = data.load('gravel')
P, R = 8, 1
def compute_lbp_hist(img):
lbp = local_binary_pattern(img, P, R, method='uniform')
hist, _ = np.histogram(lbp.ravel(), bins=59, range=[0, 59])
hist = hist / hist.sum()
return hist, lbp
fig, axes = plt.subplots(3, 3, figsize=(12, 12))
for i, (img, name) in enumerate([(brick, '砖墙'), (grass, '草地'), (gravel, '砾石')]):
hist, lbp = compute_lbp_hist(img)
axes[i,0].imshow(img, cmap='gray'); axes[i,0].set_title(name); axes[i,0].axis('off')
axes[i,1].imshow(lbp, cmap='gray'); axes[i,1].set_title('LBP编码'); axes[i,1].axis('off')
axes[i,2].bar(range(59), hist, width=1.0); axes[i,2].set_xlabel('模式'); axes[i,2].set_ylabel('频率')
plt.tight_layout()
plt.savefig('./results/lbp_comparison.png', dpi=150)
plt.show()
步骤8:GLCM特征提取
创建texture_glcm.py:
python
import cv2
import numpy as np
from skimage.feature import graycomatrix, graycoprops
def reduce_gray_levels(img, levels=64):
factor = 256 // levels
return (img // factor).astype(np.uint8)
img = cv2.imread("./test_texture.jpg", cv2.IMREAD_GRAYSCALE)
img_comp = reduce_gray_levels(img, 64)
glcm = graycomatrix(img_comp, distances=[1, 2, 3], angles=[0, np.pi/4, np.pi/2, 3*np.pi/4],
levels=64, symmetric=True, normed=True)
contrast = graycoprops(glcm, 'contrast')
correlation = graycoprops(glcm, 'correlation')
energy = graycoprops(glcm, 'energy')
homogeneity = graycoprops(glcm, 'homogeneity')
print("GLCM特征(距离=1,方向0°)")
print(f"对比度: {contrast[0,0]:.4f}, 相关性: {correlation[0,0]:.4f}, "
f"能量: {energy[0,0]:.4f}, 同质性: {homogeneity[0,0]:.4f}")
3.5 光照变换
步骤9:直方图均衡化与CLAHE
创建illumination_transform.py:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("./test_image.jpg", cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
img_he = cv2.equalizeHist(img)
# CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_clahe = clahe.apply(img)
# 伽马校正
def gamma_correction(img, gamma):
inv_gamma = 1.0 / gamma
table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype(np.uint8)
return cv2.LUT(img, table)
img_gamma_05 = gamma_correction(img, 0.5) # 变亮
img_gamma_20 = gamma_correction(img, 2.0) # 变暗
# 显示
plt.figure(figsize=(15,10))
titles = ['原始', 'HE', 'CLAHE', 'Gamma=0.5', 'Gamma=2.0']
images = [img, img_he, img_clahe, img_gamma_05, img_gamma_20]
for i in range(5):
plt.subplot(2,3,i+1); plt.imshow(images[i], cmap='gray'); plt.title(titles[i]); plt.axis('off')
# 绘制直方图
plt.subplot(2,3,i+6); plt.hist(images[i].ravel(), bins=256, range=[0,256], color='black')
plt.xlim([0,256])
plt.tight_layout()
plt.savefig('./results/illumination_transform.png', dpi=150)
plt.show()
步骤10:光照变换对LBP特征的影响分析
创建texture_illumination_robustness.py,对比不同光照变换后的LBP直方图差异:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage.feature import local_binary_pattern
img = cv2.imread("./test_texture.jpg", cv2.IMREAD_GRAYSCALE)
img_he = cv2.equalizeHist(img)
clahe = cv2.createCLAHE(clipLimit=2.0).apply(img)
img_gamma = gamma_correction(img, 0.5) # 使用上面定义的函数
def get_lbp_hist(img):
lbp = local_binary_pattern(img, 8, 1, method='uniform')
hist, _ = np.histogram(lbp.ravel(), bins=59, range=[0,59])
return hist / hist.sum()
hists = [get_lbp_hist(i) for i in [img, img_he, img_clahe, img_gamma]]
labels = ['原始', 'HE', 'CLAHE', 'Gamma=0.5']
plt.figure(figsize=(12,8))
for i, (hist, label) in enumerate(zip(hists, labels)):
plt.subplot(2,2,i+1); plt.bar(range(59), hist, width=1.0)
plt.title(f'{label} LBP直方图'); plt.xlabel('模式'); plt.ylabel('频率')
plt.tight_layout()
plt.savefig('./results/lbp_illumination_effect.png', dpi=150)
plt.show()
四、实验验收标准
4.1 环境与工具验收
- OpenCV、scikit-image等库正确安装并能导入
- 摄像头或视频文件正常读取
4.2 功能模块验收
- 视频流接入:能成功捕获摄像头或读取视频文件,并能按指定间隔提取帧保存为图片
- 图像基本操作:能完成灰度转换、尺寸调整、保存等操作
- 降噪与边缘增强:能展示四种滤波方法的对比结果,能展示Sobel、Laplacian、Canny边缘检测结果,并能实现边缘增强
- 纹理特征提取:能生成LBP编码图和直方图,能计算GLCM的对比度、能量、同质性等特征
- 光照变换:能展示HE、CLAHE、伽马校正的效果及直方图变化,能分析光照变换对LBP特征的影响
4.3 代码与报告质量
- 代码结构清晰,关键步骤有注释
- 实验结果图片保存完整(建议保存至
./results/目录) - 实验报告包含各模块的核心代码、结果截图和分析讨论
五、常见问题与解决方案
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
cv2.VideoCapture(0)无法打开 |
摄像头被占用或驱动问题 | 关闭其他使用摄像头的程序;检查设备管理器 |
| 视频文件读取失败 | 路径错误或编码不支持 | 使用绝对路径;转换为H.264编码的MP4 |
| SIFT不可用(若使用) | OpenCV版本低于4.5.0 | 本实验未使用SIFT,无需担心 |
skimage.data.load('brick')报错 |
网络问题或数据未缓存 | 使用本地图片替代,或安装pooch库 |
| GLCM计算内存不足 | 灰度级别过高(256) | 使用reduce_gray_levels压缩至64或32级 |
| CLAHE处理后对比度过强 | clipLimit值过大 |
降低clipLimit(如0.5~2.0) |
| 实时处理帧率低 | 图像尺寸大或算法复杂 | 缩小处理尺寸;减少滤波核大小 |
六、实验总结与思考
- 记录结果 :记录各模块的最优参数配置(如滤波核大小、Canny阈值、CLAHE的
clipLimit值)及处理效果。对比不同滤波方法对高斯噪声和椒盐噪声的适用性,分析哪种方法更适合边缘保持。 - 问题思考 :
- 在视频分帧提取中,帧间隔过大或过小分别会带来什么影响?如何根据应用场景选择合适的间隔?
- 中值滤波为什么对椒盐噪声抑制效果显著,而对高斯噪声效果一般?
- Canny边缘检测中的双阈值(低阈值和高阈值)对检测结果有何影响?如何选择合理的阈值?
- LBP的均匀模式(Uniform LBP)相比原始LBP有什么优势?在什么情况下应该使用均匀模式?
- 直方图均衡化和CLAHE的主要区别是什么?CLAHE中的
tileGridSize参数如何影响输出? - 光照变换对LBP特征的鲁棒性有何影响?哪种光照变换方法更有利于保持纹理特征的稳定性?
- 实验报告要求 :
- 提交PDF格式实验报告,包含每个模块的关键代码、处理前后对比图、参数分析及对思考题的回答。
- 同时提交完整代码压缩包,确保代码可重复运行。
提交格式 :PDF文档 + 代码压缩包
提交时间 :实验完成后一周内
评分标准:环境配置(10%) + 各模块功能实现(50%) + 结果分析与可视化(20%) + 实验报告质量(20%)