Python 机器视觉进阶:第11-13章完整学习指南
Python 机器视觉进阶:第11-13章完整学习指南
恭喜你完成了OpenCV基础部分的学习!现在你已经掌握了图像处理、特征提取和轮廓分析等核心技能。接下来的三章将带你进入机器视觉的更高级领域:相机标定与三维视觉、实时视频处理以及深度学习与DNN模块。
第11章:相机标定与三维视觉
相机标定是机器视觉中的基础但至关重要的步骤。现实中的相机镜头会引入畸变,而标定的目的就是获取相机内部参数和畸变系数,从而校正图像,并为三维重建打下基础。
11.1 相机模型与畸变原理
重点:真实相机使用透镜成像,这会导致两种主要畸变:
- 径向畸变:光线在透镜边缘弯曲程度更大,导致图像边缘的直线变弯曲。用参数 (k_1, k_2, k_3) 表示。
- 切向畸变:透镜与成像平面不平行导致,用参数 (p_1, p_2) 表示。
相机标定的目的:获取相机内参矩阵(焦距、主点坐标)和畸变系数,用于图像校正和三维信息恢复。
11.2 使用棋盘格进行相机标定
重点:标定需要拍摄多张不同角度的棋盘格图案,OpenCV会自动检测角点并计算参数。
import cv2
import numpy as np
import glob
# 1. 准备标定板参数
# 棋盘格内角点数(例如9x6的棋盘,内角点为8x5,这里以9x6为例)
chessboard_size = (9, 6) # 内角点数量
square_size = 25 # 每个方格的实际尺寸(毫米),用于后续三维重建
# 2. 准备对象点(世界坐标系中的三维点)
# 假设棋盘格在Z=0平面上,生成(0,0,0), (1,0,0), ... 坐标
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
objp = objp * square_size # 乘以实际尺寸
# 存储所有图像的对象点和图像点
objpoints = [] # 三维点
imgpoints = [] # 二维点
# 3. 读取标定图像
images = glob.glob('calibration_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 4. 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
if ret:
objpoints.append(objp)
# 5. 亚像素级角点精细化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners2)
# 6. 绘制角点并显示
cv2.drawChessboardCorners(img, chessboard_size, corners2, ret)
cv2.imshow('Corners', img)
cv2.waitKey(100)
cv2.destroyAllWindows()
# 7. 执行相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print(f"重投影误差: {ret}")
print(f"相机内参矩阵:\n{mtx}")
print(f"畸变系数: {dist.ravel()}")
重点:
cv2.findChessboardCorners():检测棋盘格角点,返回角点坐标cv2.cornerSubPix():亚像素级角点精细化,提高标定精度cv2.calibrateCamera():执行标定,返回内参矩阵和畸变系数
11.3 畸变校正(去畸变)
获取相机参数后,就可以对图像进行畸变校正。
# 读取一张待校正的图像
img = cv2.imread('distorted_image.jpg')
h, w = img.shape[:2]
# 1. 获取优化后的相机矩阵(可以调整alpha参数)
# alpha=0 时,返回的图像裁剪掉黑色边框,保留有效像素
# alpha=1 时,保留所有原始像素,但有黑色边框
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# 2. 方法一:使用 undistort 直接校正
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# 3. 裁剪图像(去除黑色边框)
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
# 4. 方法二:使用 remap 进行校正(适合视频流,可预先计算映射)
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst2 = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# 显示结果对比
cv2.imshow('Original', img)
cv2.imshow('Undistorted', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
重点:
cv2.getOptimalNewCameraMatrix():根据alpha参数优化相机矩阵,决定是否保留黑色边框cv2.undistort():直接对单张图像进行畸变校正cv2.initUndistortRectifyMap()+cv2.remap():预先计算映射表,适合批量处理或视频流
11.4 立体视觉基础(双目相机)
重点:双目视觉通过两个相机从不同角度拍摄同一场景,计算视差来恢复深度信息。
11.4.1 立体标定
# 假设已经对左右相机分别进行了单目标定
# 需要额外标定两个相机之间的相对位置关系(旋转矩阵R和平移向量T)
# 立体标定
ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate(
objpoints, # 世界坐标系中的三维点
imgpoints_left, # 左图像角点
imgpoints_right, # 右图像角点
cameraMatrix1, # 左相机内参
distCoeffs1, # 左相机畸变系数
cameraMatrix2, # 右相机内参
distCoeffs2, # 右相机畸变系数
image_size, # 图像尺寸
criteria, flags
)
11.4.2 立体校正
立体校正是为了消除左右图像的行不对齐问题,使得对应点在同一水平线上,便于计算视差。
# 立体校正
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
K1, D1, K2, D2, image_size, R, T,
alpha=0, flags=cv2.CALIB_ZERO_DISPARITY
)
# 计算校正映射
map1x, map1y = cv2.initUndistortRectifyMap(K1, D1, R1, P1, image_size, cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, D2, R2, P2, image_size, cv2.CV_32FC1)
# 应用校正
rectified_left = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
rectified_right = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)
11.4.3 视差图与深度计算
# 1. 创建立体匹配对象(使用SGBM算法)
stereo = cv2.StereoSGBM_create(
minDisparity=0,
numDisparities=16*5, # 视差范围,必须是16的倍数
blockSize=21, # 匹配块大小
P1=8*3*21**2, # 视差平滑度参数
P2=32*3*21**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32
)
# 2. 计算视差图
disparity = stereo.compute(rectified_left, rectified_right).astype(np.float32) / 16.0
# 3. 将视差图转换为深度图(需要Q矩阵)
depth_map = cv2.reprojectImageTo3D(disparity, Q)
# 4. 显示视差图
cv2.imshow('Disparity', (disparity - disparity.min()) / (disparity.max() - disparity.min()))
重点:
- 视差:左右图像中对应点的水平位置差异,与深度成反比
cv2.StereoSGBM_create():半全局块匹配算法,比BM更准确cv2.reprojectImageTo3D():将视差图转换为三维点云
11.5 综合案例:简单三维重建
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def simple_3d_reconstruction(left_img, right_img, K1, D1, K2, D2, R, T):
"""简单的双目三维重建流程"""
# 1. 立体校正
image_size = left_img.shape[:2][::-1]
R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
K1, D1, K2, D2, image_size, R, T, alpha=0
)
# 2. 计算校正映射
map1x, map1y = cv2.initUndistortRectifyMap(K1, D1, R1, P1, image_size, cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, D2, R2, P2, image_size, cv2.CV_32FC1)
# 3. 校正图像
rect_left = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
rect_right = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)
# 4. 计算视差
stereo = cv2.StereoSGBM_create(
minDisparity=0,
numDisparities=16*5,
blockSize=15,
P1=8*3*15**2,
P2=32*3*15**2
)
disparity = stereo.compute(rect_left, rect_right).astype(np.float32) / 16.0
# 5. 计算三维点云
points_3d = cv2.reprojectImageTo3D(disparity, Q)
# 6. 简单点云可视化
mask = disparity > disparity.min()
x = points_3d[:, :, 0][mask]
y = points_3d[:, :, 1][mask]
z = points_3d[:, :, 2][mask]
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x[::10], y[::10], z[::10], c=z[::10], cmap='viridis', s=1)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.title('3D Reconstruction')
plt.show()
return disparity, points_3d
第12章:视频处理与实时采集
将图像处理算法应用到视频流或实时摄像头,是实现工业检测系统的基础。
12.1 从摄像头捕获实时视频
import cv2
# 1. 创建 VideoCapture 对象
# 参数0表示第一个摄像头(通常是内置摄像头),1表示第二个,依此类推
cap = cv2.VideoCapture(0)
# 检查摄像头是否成功打开
if not cap.isOpened():
print("无法打开摄像头")
exit()
# 2. 设置摄像头属性(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) # 设置宽度
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 设置高度
cap.set(cv2.CAP_PROP_FPS, 30) # 设置帧率
while True:
# 3. 逐帧捕获
ret, frame = cap.read()
# 如果帧读取正确,ret为True
if not ret:
print("无法接收帧,退出...")
break
# 4. 在这里对帧进行处理
# 例如转换为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 5. 显示结果
cv2.imshow('Camera Feed', gray)
# 6. 按'q'键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 7. 释放资源
cap.release()
cv2.destroyAllWindows()
重点:
cv2.VideoCapture(0):打开摄像头,参数为设备索引cap.read():读取一帧,返回布尔值和图像数据cap.set(propId, value):设置摄像头属性,如分辨率、帧率cv2.waitKey(1):等待1毫秒,用于检测按键和维持窗口显示
12.2 从视频文件读取
import cv2
# 从视频文件读取
cap = cv2.VideoCapture('test_video.mp4')
# 获取视频属性
fps = cap.get(cv2.CAP_PROP_FPS) # 帧率
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 总帧数
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 宽度
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 高度
print(f"视频信息: {width}x{height}, {fps}fps, 总帧数: {frame_count}")
# 计算每帧的显示延迟(毫秒)
delay = int(1000 / fps)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print("视频播放完毕")
break
# 处理帧
processed = cv2.Canny(frame, 100, 200) # 边缘检测
cv2.imshow('Video', processed)
# 使用基于帧率的延迟
if cv2.waitKey(delay) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
重点:
cap.get(propId):获取视频属性,如帧率、尺寸- 使用视频的实际帧率计算
waitKey延迟,实现正常速度播放
12.3 保存处理后的视频
import cv2
# 打开摄像头
cap = cv2.VideoCapture(0)
# 定义视频编码器和输出文件
fourcc = cv2.VideoWriter_fourcc(*'XVID') # 编码器
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 处理帧(例如水平翻转)
flipped = cv2.flip(frame, 1)
# 写入输出视频
out.write(flipped)
cv2.imshow('Recording', flipped)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
out.release() # 记得释放 VideoWriter
cv2.destroyAllWindows()
重点:
cv2.VideoWriter_fourcc():定义视频编码格式cv2.VideoWriter():创建视频写入对象,指定输出文件名、编码器、帧率和尺寸out.write():写入处理后的帧
12.4 实时视频处理性能优化
实时视频处理需要保证处理速度不低于帧率,否则会出现卡顿。
import cv2
import time
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# FPS计算变量
fps_counter = 0
fps_start_time = time.time()
fps = 0
while True:
# 计算帧率
fps_counter += 1
if time.time() - fps_start_time > 1.0:
fps = fps_counter
fps_counter = 0
fps_start_time = time.time()
ret, frame = cap.read()
if not ret:
break
# 优化1:调整处理分辨率
# 如果处理算法很耗时,可以先缩小图像
small_frame = cv2.resize(frame, (320, 240))
# 在这里进行耗时处理
# 例如边缘检测
edges = cv2.Canny(small_frame, 100, 200)
# 优化2:跳过部分帧(例如每处理一帧,跳过两帧)
# 但这会影响实时性,需根据需求权衡
# 在原始帧上显示FPS
cv2.putText(frame, f"FPS: {fps}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Optimized Processing', frame)
cv2.imshow('Edges', edges)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
重点:
- 降低分辨率处理:先缩小图像再进行算法处理,再映射回原图
- 帧率计算:用于监控性能,确保处理速度达标
- 跳帧处理:当算法非常耗时时,可以间隔处理,但会损失实时性
12.5 综合案例:实时运动检测
import cv2
import numpy as np
class MotionDetector:
def __init__(self, threshold=25, min_area=500):
self.threshold = threshold
self.min_area = min_area
self.first_frame = None
def detect(self, frame):
"""检测运动区域"""
# 转换为灰度并高斯模糊
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
# 如果是第一帧,保存作为背景
if self.first_frame is None:
self.first_frame = gray
return frame, []
# 计算当前帧与背景的差异
frame_delta = cv2.absdiff(self.first_frame, gray)
thresh = cv2.threshold(frame_delta, self.threshold, 255, cv2.THRESH_BINARY)[1]
# 形态学操作去除噪声
thresh = cv2.dilate(thresh, None, iterations=2)
thresh = cv2.erode(thresh, None, iterations=2)
# 查找轮廓
contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 筛选符合条件的运动区域
motion_areas = []
for contour in contours:
if cv2.contourArea(contour) < self.min_area:
continue
# 获取外接矩形
(x, y, w, h) = cv2.boundingRect(contour)
motion_areas.append((x, y, w, h))
return thresh, motion_areas
# 主程序
cap = cv2.VideoCapture(0)
detector = MotionDetector(threshold=25, min_area=500)
while True:
ret, frame = cap.read()
if not ret:
break
# 检测运动
thresh, motion_areas = detector.detect(frame)
# 在帧上绘制运动区域
for (x, y, w, h) in motion_areas:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.putText(frame, "Motion", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 显示结果
cv2.imshow('Motion Detection', frame)
cv2.imshow('Threshold', thresh)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
工业应用场景:硅片检测设备中,可以在硅片进入检测区域时触发图像采集,代替外部传感器。
第13章:深度学习与OpenCV DNN模块
OpenCV的DNN(Deep Neural Network)模块可以直接加载预训练的深度学习模型进行推理,无需安装TensorFlow、PyTorch等深度学习框架。
13.1 DNN模块支持的模型格式
重点:OpenCV DNN模块支持多种深度学习框架的模型格式:
- Caffe:
.caffemodel+.prototxt - TensorFlow:
.pb(冻结图)或SavedModel - Torch/PyTorch:需转换为ONNX格式
- Darknet:
.weights+.cfg(YOLO系列) - ONNX:开放神经网络交换格式(推荐,兼容性好)
13.2 加载模型并进行图像分类
import cv2
import numpy as np
# 1. 加载预训练模型
# 以GoogleNet为例(Caffe格式)
net = cv2.dnn.readNetFromCaffe('bvlc_googlenet.prototxt', 'bvlc_googlenet.caffemodel')
# 或者加载ONNX格式
# net = cv2.dnn.readNetFromONNX('model.onnx')
# 2. 读取图像并预处理
image = cv2.imread('cat.jpg')
blob = cv2.dnn.blobFromImage(
image,
scalefactor=1.0/255, # 缩放因子
size=(224, 224), # 输入尺寸
mean=(104, 117, 123), # 均值(不同模型不同)
swapRB=True, # 交换RB通道(OpenCV是BGR,模型需要RGB)
crop=False
)
# 3. 设置网络输入
net.setInput(blob)
# 4. 前向传播,获取输出
output = net.forward()
# 5. 解析结果
# 假设是1000类的分类任务
predictions = output[0]
class_id = np.argmax(predictions)
confidence = predictions[class_id]
# 加载类别标签
with open('imagenet_classes.txt', 'r') as f:
classes = [line.strip() for line in f.readlines()]
print(f"预测类别: {classes[class_id]}, 置信度: {confidence:.4f}")
# 在图像上显示结果
cv2.putText(image, f"{classes[class_id]}: {confidence:.2f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Classification', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
重点:
cv2.dnn.blobFromImage():将图像转换为深度学习模型需要的blob格式,包括缩放、裁剪、均值归一化等net.setInput():设置网络输入net.forward():执行前向推理,获取输出
13.3 使用PyTorch模型(转换为ONNX)
重点:PyTorch模型需要通过ONNX格式才能在OpenCV中使用。
# 在PyTorch中将模型转换为ONNX
import torch
import torchvision.models as models
# 1. 加载预训练PyTorch模型
model = models.resnet50(pretrained=True)
model.eval()
# 2. 创建示例输入
dummy_input = torch.randn(1, 3, 224, 224)
# 3. 导出ONNX
torch.onnx.export(
model,
dummy_input,
"resnet50.onnx",
export_params=True,
opset_version=11,
input_names=['input'],
output_names=['output']
)
print("模型已转换为ONNX格式")
# 在OpenCV中加载ONNX模型
import cv2
import numpy as np
# 加载ONNX模型
net = cv2.dnn.readNetFromONNX('resnet50.onnx')
# 图像预处理(需与PyTorch保持一致)
image = cv2.imread('image.jpg')
image = cv2.resize(image, (256, 256))
# 中心裁剪为224x224
h, w = image.shape[:2]
start_h = (h - 224) // 2
start_w = (w - 224) // 2
image = image[start_h:start_h+224, start_w:start_w+224]
# 转换为blob,注意PyTorch的预处理顺序:归一化到[0,1],减去均值,除以标准差
blob = cv2.dnn.blobFromImage(
image,
scalefactor=1.0/255,
size=(224, 224),
mean=(0.485, 0.456, 0.406), # ImageNet均值
swapRB=True
)
# 除以标准差
blob[0] /= np.array([0.229, 0.224, 0.225]).reshape(3, 1, 1)
net.setInput(blob)
output = net.forward()
13.4 目标检测(YOLO)实战
重点:YOLO(You Only Look Once)是最流行的实时目标检测算法。
import cv2
import numpy as np
class YOLODetector:
def __init__(self, weights_path, config_path, classes_path, conf_threshold=0.5, nms_threshold=0.4):
# 加载网络
self.net = cv2.dnn.readNetFromDarknet(config_path, weights_path)
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
# 加载类别
with open(classes_path, 'r') as f:
self.classes = [line.strip() for line in f.readlines()]
# 获取输出层名称
self.output_layers = self.get_output_layers()
self.conf_threshold = conf_threshold
self.nms_threshold = nms_threshold
def get_output_layers(self):
"""获取YOLO的输出层名称"""
layer_names = self.net.getLayerNames()
output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()]
return output_layers
def detect(self, image):
"""执行目标检测"""
h, w = image.shape[:2]
# 创建blob
blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), swapRB=True, crop=False)
# 前向传播
self.net.setInput(blob)
outputs = self.net.forward(self.output_layers)
# 解析检测结果
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > self.conf_threshold:
# YOLO输出格式:center_x, center_y, width, height (归一化坐标)
center_x = int(detection[0] * w)
center_y = int(detection[1] * h)
width = int(detection[2] * w)
height = int(detection[3] * h)
# 转换为左上角坐标
x = int(center_x - width / 2)
y = int(center_y - height / 2)
boxes.append([x, y, width, height])
confidences.append(float(confidence))
class_ids.append(class_id)
# 非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, self.conf_threshold, self.nms_threshold)
results = []
if len(indices) > 0:
for i in indices.flatten():
x, y, w, h = boxes[i]
results.append({
'box': (x, y, w, h),
'confidence': confidences[i],
'class_id': class_ids[i],
'class_name': self.classes[class_ids[i]]
})
return results
def draw_detections(self, image, detections):
"""在图像上绘制检测结果"""
for det in detections:
x, y, w, h = det['box']
label = f"{det['class_name']}: {det['confidence']:.2f}"
# 绘制矩形
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 绘制标签背景
(label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
cv2.rectangle(image, (x, y-label_h-10), (x+label_w, y), (0, 255, 0), -1)
# 绘制标签文字
cv2.putText(image, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2)
return image
# 使用示例
detector = YOLODetector(
weights_path='yolov3.weights',
config_path='yolov3.cfg',
classes_path='coco.names'
)
# 对图像进行检测
img = cv2.imread('scene.jpg')
detections = detector.detect(img)
result_img = detector.draw_detections(img, detections)
cv2.imshow('YOLO Detection', result_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
重点:
cv2.dnn.readNetFromDarknet():加载YOLO的Darknet模型net.getUnconnectedOutLayers():获取YOLO的输出层名称(YOLO有多个输出层)- 非极大值抑制(NMS):去除重叠的检测框,保留最佳结果
13.5 DNN模块在视频流中的应用
import cv2
import numpy as np
# 加载YOLO模型
net = cv2.dnn.readNetFromDarknet('yolov3-tiny.cfg', 'yolov3-tiny.weights')
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
# 获取输出层
layer_names = net.getLayerNames()
output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers()]
# 加载类别
with open('coco.names', 'r') as f:
classes = [line.strip() for line in f.readlines()]
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
h, w = frame.shape[:2]
# 创建blob
blob = cv2.dnn.blobFromImage(frame, 1/255.0, (416, 416), swapRB=True, crop=False)
# 前向传播
net.setInput(blob)
outputs = net.forward(output_layers)
# 解析结果
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.5:
center_x = int(detection[0] * w)
center_y = int(detection[1] * h)
width = int(detection[2] * w)
height = int(detection[3] * h)
x = int(center_x - width / 2)
y = int(center_y - height / 2)
boxes.append([x, y, width, height])
confidences.append(float(confidence))
class_ids.append(class_id)
# NMS
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}"
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 显示FPS(简化计算)
cv2.putText(frame, "YOLO Real-time Detection", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('YOLO Live', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
13.6 综合案例:硅片缺陷检测原型
结合本章所学,实现一个简单的硅片缺陷检测原型:
import cv2
import numpy as np
class SiliconWaferInspector:
def __init__(self, model_path=None):
"""初始化检测器"""
# 如果有深度学习模型,可以在这里加载
if model_path:
self.net = cv2.dnn.readNetFromONNX(model_path)
else:
self.net = None
# 传统视觉参数
self.defect_threshold = 30
def preprocess(self, image):
"""图像预处理"""
# 转为灰度
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 高斯滤波去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 1.5)
return blurred
def detect_defects_traditional(self, image):
"""传统视觉方法检测缺陷"""
processed = self.preprocess(image)
# 自适应阈值分割
thresh = cv2.adaptiveThreshold(
processed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2
)
# 形态学操作去除小噪点
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
defects = []
for contour in contours:
area = cv2.contourArea(contour)
if area > 50: # 面积大于50像素认为是缺陷
x, y, w, h = cv2.boundingRect(contour)
defects.append({
'box': (x, y, w, h),
'area': area,
'type': 'scratch' if w > h * 2 else 'dot' # 简单分类
})
return defects
def detect_defects_deeplearning(self, image):
"""深度学习方法检测缺陷(需训练好的模型)"""
if self.net is None:
return []
# 预处理
blob = cv2.dnn.blobFromImage(image, 1/255.0, (224, 224), swapRB=True)
self.net.setInput(blob)
outputs = self.net.forward()
# 解析输出(假设是缺陷分割或检测结果)
# 这里简化处理,实际需要根据模型输出格式解析
return outputs
def inspect(self, image, method='traditional'):
"""执行检测"""
if method == 'traditional':
defects = self.detect_defects_traditional(image)
else:
defects = self.detect_defects_deeplearning(image)
# 绘制结果
result = image.copy()
for defect in defects:
x, y, w, h = defect['box']
color = (0, 0, 255) if defect['type'] == 'scratch' else (0, 255, 255)
cv2.rectangle(result, (x, y), (x+w, y+h), color, 2)
cv2.putText(result, defect['type'], (x, y-5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
# 统计信息
cv2.putText(result, f"Defects: {len(defects)}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return result, defects
# 使用示例
inspector = SiliconWaferInspector()
# 读取硅片图像
wafer_img = cv2.imread('wafer_sample.jpg')
# 执行检测
result_img, defects = inspector.inspect(wafer_img)
# 显示结果
cv2.imshow('Wafer Inspection', result_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
print(f"检测到 {len(defects)} 处缺陷")
学习建议与下一阶段预告
恭喜你完成了第11-13章的学习!现在你已经掌握了机器视觉的高级技术:
- 相机标定与三维视觉:能够校正图像畸变,获取三维信息
- 视频处理与实时采集:能够处理实时视频流,构建实时检测系统
- 深度学习与DNN模块:能够将深度学习模型集成到OpenCV应用中
实战建议:
- 用你自己的相机拍摄棋盘格图像,完成一次完整的相机标定
- 尝试用YOLO模型对实时摄像头流进行目标检测
- 结合之前学的知识,尝试改进硅片检测原型
下一阶段展望:
你已经完成了整个Python+OpenCV机器视觉的学习路线!接下来可以:
- 深入学习深度学习框架:PyTorch或TensorFlow,训练自己的缺陷检测模型
- 系统集成:将Python视觉算法封装为服务,供C#上位机调用
- 项目实战:开发完整的硅片检测系统,集成相机控制、图像采集、缺陷检测、PLC通信、数据库存储
如果你在学习过程中遇到任何问题,或者想要深入某个专题的实战案例,随时可以提问!祝你在机器视觉领域不断精进!
更多推荐



所有评论(0)