第五部分:进阶项目实战
在前面的学习中,我们已经掌握了图像和视频的基础操作、增强滤波、特征提取以及一些基础的目标检测方法。现在,我们将综合运用这些知识来构建一些更复杂、更实用的应用项目。
这一部分的项目将结合前面学到的技术,并介绍一些新的概念和工具,包括:
- 人脸识别系统: 从识别到识别“谁”的脸。
- 图像拼接与全景图制作: 将多张有重叠的图片合成为一张大图。
- 深度学习与 OpenCV 结合: 利用 OpenCV 的 DNN 模块运行深度学习模型进行目标检测。
请注意,这些项目本身都是复杂领域,本部分将提供入门级别的实现,让你了解其基本流程和关键技术。每个实战项目都需要你准备相应的图片或模型文件。
OpenCV 进阶项目实战 (第五部分)
欢迎来到 OpenCV 教程的第五部分!现在你已经具备了坚实的 OpenCV 基础知识。是时候将这些知识融会贯通,应用于一些更高级、更有趣的计算机视觉项目了。
本部分将带你构建三个常见的计算机视觉应用:一个简单的人脸识别系统、一个图像拼接工具和一个基于深度学习的对象检测应用。这些项目将帮助你理解如何将不同的 OpenCV 功能组合起来解决实际问题。
1. 人脸识别系统
人脸识别是一个重要的计算机视觉任务,广泛应用于安全、身份验证、社交媒体等领域。人脸识别与我们之前学过的人脸检测不同,检测只是找到人脸的位置,而识别是确定这张脸属于“谁”。
一个典型的人脸识别系统流程包括:
- 人脸检测: 在图像中找到人脸的位置(边界框)。
- 人脸对齐 (Alignment): 将检测到的人脸区域进行标准化处理,例如旋转、缩放,使得眼睛、鼻子等关键点位于大致相同的位置,以减少姿势、角度等变化的影响。
- 特征提取: 从对齐后的人脸图像中提取出能够唯一代表人脸身份的特征(人脸描述符)。
- 特征匹配/比较: 将当前人脸的特征与一个已知人脸库中的特征进行比较,找到最相似的特征,从而确定身份。
我们将构建一个基于 OpenCV 传统方法的简单人脸识别系统,使用 LBPH (Local Binary Patterns Histograms) 人脸识别器。这个方法相对简单,适合入门,尽管现代方法通常使用深度学习。
Python
import cv2
import numpy as np
import os# --- 练习 1.1: 人脸检测与对齐 (简单) ---
# 这一步主要基于前面学过的级联分类器进行检测和裁剪# 1. 加载人脸检测器 (使用 Haarcascade)
# 确保你有 haarcascade_frontalface_default.xml 文件
cascade_path = 'haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier()if not face_cascade.load(cv2.samples.findFile(cascade_path)):print(f"错误: 无法加载人脸检测器文件: {cascade_path}")# sys.exit(1) # 如果找不到文件,程序退出face_cascade = None # 设置为 None 方便后续检查# 2. 定义一个函数来检测和裁剪人脸
def detect_and_crop_face(image_gray, face_detector):"""在灰度图像中检测人脸并返回裁剪后的人脸区域列表。"""if face_detector is None:return [] # 如果检测器未加载,返回空列表# 检测人脸faces = face_detector.detectMultiScale(image_gray,scaleFactor=1.1,minNeighbors=5,minSize=(30, 30))cropped_faces = []for (x, y, w, h) in faces:# 裁剪人脸区域cropped_face = image_gray[y:y+h, x:x+w]# 可以选择性地调整大小,以便于后续特征提取# cropped_face = cv2.resize(cropped_face, (100, 100)) # 调整到固定大小cropped_faces.append(cropped_face)return cropped_faces# --- 在实际人脸识别系统中,你会调用上面的函数处理训练和测试图片 ---
print("\n--- 练习 1.1 完成 (人脸检测和裁剪函数已定义) ---")
# 实际运行将在实战部分展示# --- 练习 1.2: 特征提取与匹配 (使用 LBPH) ---
# LBPH 是 OpenCV face 模块的一部分,它内部处理特征提取和匹配# 1. 创建 LBPH 人脸识别器
# 需要安装 opencv-contrib-python
# pip install opencv-contrib-python
try:recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, # 局部二进制模式的半径neighbors=8, # 使用的邻居数量grid_x=8, # 直方图网格大小grid_y=8,threshold=100 # 识别阈值,距离小于此值认为匹配成功)print("\nLBPH 人脸识别器已创建。")
except AttributeError:print("\n错误: 请确保你已安装 opencv-contrib-python 库。")recognizer = None # 设置为 None 方便后续检查# --- 在实际人脸识别系统中,你会调用 recognizer.train() 和 recognizer.predict() ---
print("\n--- 练习 1.2 完成 (LBPH 识别器已创建) ---")
# 实际运行将在实战部分展示# --- 练习 1.3: 实战:简单人脸识别应用 ---# 这个实战需要一个数据集。建议按照以下结构创建文件夹:
# dataset/
# -- person1/
# ---- img1.jpg
# ---- img2.jpg
# ---- ...
# -- person2/
# ---- img1.jpg
# ---- img2.jpg
# ---- ...dataset_path = 'dataset' # 替换成你的数据集文件夹路径
if not os.path.exists(dataset_path):print(f"\n错误: 数据集文件夹未找到: {dataset_path}")print("请创建 'dataset' 文件夹,并在其中创建子文件夹 (如 person1, person2),每个子文件夹放入该人物的照片。")print("跳过人脸识别实战。")run_face_recognition_실전 = False
else:run_face_recognition_실전 = Trueif run_face_recognition_실전 and face_cascade is not None and recognizer is not None:print("\n--- 实战练习: 简单人脸识别应用 ---")images = [] # 用于存储训练图像labels = [] # 用于存储对应的标签 (人物ID)label_map = {} # 用于存储人物ID到姓名的映射current_id = 0print("正在加载和预处理训练数据...")# 遍历数据集文件夹for person_name in os.listdir(dataset_path):person_dir = os.path.join(dataset_path, person_name)if os.path.isdir(person_dir):if person_name not in label_map:label_map[person_name] = current_idcurrent_id += 1label = label_map[person_name]# 遍历人物文件夹中的图片for image_name in os.listdir(person_dir):image_path = os.path.join(person_dir, image_name)try:img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)# 检测并裁剪人脸 (使用前面定义的函数)cropped_faces = detect_and_crop_face(img, face_cascade)# 对于简单的系统,假设每张训练图只有一个人脸if len(cropped_faces) > 0:# 对于 LBPH,输入图像需要是固定大小# 如果 detect_and_crop_face 没有调整大小,这里需要调整face_img = cv2.resize(cropped_faces[0], (100, 100)) # 调整大小到100x100images.append(face_img)labels.append(label)# print(f"已处理 {image_path} 提取人脸,标签: {label}")else:# print(f"警告: 未在 {image_path} 中检测到人脸,跳过。")passexcept Exception as e:print(f"处理图片 {image_path} 时发生错误: {e}")print(f"共加载 {len(images)} 张训练人脸图像。")print(f"人物ID映射: {label_map}")if len(images) > 0:# 训练人脸识别器print("正在训练人脸识别器...")recognizer.train(images, np.array(labels))print("训练完成。")# --- 进行人脸识别测试 ---test_image_path = 'your_test_image.jpg' # 替换成你要测试的图片路径# 尝试找一张包含数据集中人物或陌生人脸的图片try:test_img_color = cv2.imread(test_image_path)if test_img_color is None:raise FileNotFoundError(f"测试图片文件未找到: {test_image_path}")test_img_gray = cv2.cvtColor(test_img_color, cv2.COLOR_BGR2GRAY)output_img = test_img_color.copy()# 在测试图像中检测人脸test_faces = face_cascade.detectMultiScale(test_img_gray,scaleFactor=1.1,minNeighbors=5,minSize=(30, 30))print(f"\n在测试图像中检测到 {len(test_faces)} 个人脸。")# 对检测到的每张人脸进行识别for (x, y, w, h) in test_faces:cropped_test_face = test_img_gray[y:y+h, x:x+w]# 调整大小与训练图像一致cropped_test_face = cv2.resize(cropped_test_face, (100, 100))# 进行预测# label: 预测的人物ID# confidence: 置信度/距离,值越小越相似predicted_label, confidence = recognizer.predict(cropped_test_face)# 根据置信度判断是否是已知人物# recognition_threshold = recognizer.getThreshold() # 获取训练时设置的阈值recognition_threshold = 100 # 或者使用训练时设置的阈值# 查找人物名称predicted_name = "未知"for name, lbl in label_map.items():if lbl == predicted_label:predicted_name = namebreak# 根据置信度判断是否识别成功if confidence < recognition_threshold:display_text = f"{predicted_name} ({confidence:.2f})"box_color = (0, 255, 0) # 绿色框表示识别成功else:display_text = f"未知 ({confidence:.2f})"box_color = (0, 0, 255) # 红色框表示未知# 在图像上绘制边界框和预测结果cv2.rectangle(output_img, (x, y), (x+w, y+h), box_color, 2)cv2.putText(output_img, display_text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, box_color, 2)# 显示结果cv2.imshow('Face Recognition Result', output_img)cv2.waitKey(0)cv2.destroyAllWindows()except FileNotFoundError:print(f"错误: 测试图片文件未找到: {test_image_path}")except Exception as e:print(f"处理测试图片时发生错误: {e}")else:print("训练数据不足,无法进行人脸识别。请检查数据集文件夹。")else:print("\n跳过人脸识别实战,请确保数据集、人脸检测器和 LBPH 识别器已准备就绪。")print("\n--- 实战练习: 简单人脸识别应用 完成 ---")
实战提示:
- 准备数据集: 这是最关键的一步。创建一个名为
dataset
的文件夹,并在其中创建与每个人物对应的子文件夹(例如person1
,person2
,zhangsan
,lisi
)。将每个人的多张照片放入其对应的子文件夹中。照片应包含清晰的人脸,尽量在不同角度、光照下拍摄。每人至少需要几张照片用于训练。 - 确保人脸检测器文件存在:
haarcascade_frontalface_default.xml
文件应该在你的代码同一目录下,或者你在代码中提供了正确的完整路径。 - 安装
opencv-contrib-python
: LBPH 人脸识别器在opencv-contrib-python
库中。 - 设置测试图片路径: 修改
test_image_path
为你要测试的图片路径。 - 这个系统是基于传统方法的简单实现,对于复杂场景和大规模数据集,性能有限。现代人脸识别通常使用深度学习模型。
2. 图像拼接与全景图制作
图像拼接是将多张有重叠区域的图像合并成一张视野更宽的图像(如全景图)的技术。
基本流程:
- 特征提取: 在每张图像中找到特征点(如 ORB, SIFT, SURF)。
- 特征匹配: 找到不同图像中对应于同一场景点的特征点对。
- 估计变换: 根据匹配的特征点对,计算图像之间的几何变换关系(如单应性变换 Homography)。如果场景是平面的,或者相机只旋转,单应性变换适用;如果场景有深度变化且相机移动,则需要更复杂的变换(如基本矩阵Fundamental Matrix)。
- 图像校正/扭曲 (Warping): 根据计算出的变换,将图像进行校正或扭曲,使其在同一个平面或透视下对齐。
- 图像融合 (Blending): 将扭曲后的图像进行融合,消除重叠区域的接缝。
OpenCV 提供了一个方便的 cv2.Stitcher
类,可以自动处理大部分这些步骤,简化全景图的制作过程。
Python
import cv2
import numpy as np
import os# --- 练习 2.1: 特征匹配与变换 (概念) ---
# 这部分概念我们在第三部分 ORB 匹配和本部分的单应性变换中已涉及
# 代码实现比较复杂,我们将在实战中使用 Stitcher 自动处理# 单应性变换 (Homography)
# 描述了两个平面之间的投影变换。在图像拼接中,如果场景是平面,或者相机绕某个点旋转,
# 那么可以通过 Homography 矩阵 H 将一个图像平面上的点 (x, y) 映射到另一个图像平面上的点 (x', y'):
# [x'] [h11 h12 h13] [x]
# [y'] = [h21 h22 h23] [y]
# [w'] [h31 h32 h33] [1]
# 其中 x' = x'/w', y' = y'/w'# OpenCV 函数:
# cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold, mask, maxIterations, confidence)
# 根据匹配的特征点对 (srcPoints 和 dstPoints) 计算 Homography 矩阵 H。
# method: 估计方法 (如 cv2.RANSAC 使用 RANSAC 算法剔除误匹配点)
#
# cv2.warpPerspective(src, M, dsize, dst, flags, borderMode, borderValue)
# 使用 Homography 矩阵 M 对图像 src 进行透视变换,输出到尺寸为 dsize 的图像 dst。print("\n--- 练习 2.1 完成 (特征匹配与变换概念已介绍) ---")# --- 练习 2.2: 图像融合技术 (概念) ---
# 融合是将重叠区域的像素值平滑过渡,避免出现明显的拼接线。# 简单融合方法:
# 1. 平均法: 在重叠区域简单平均两个图像的像素值。
# 2. 羽化 (Feathering): 在重叠区域创建渐变的权重掩膜,靠近图像边缘的权重为0,靠近中心权重为1,然后按权重融合像素。
# 3. 多波段融合 (Multi-band Blending): 将图像分解到不同频率波段,在每个波段进行融合,再合成,效果最好但最复杂。# OpenCV 的 Stitcher 类通常会采用更高级的融合方法。print("\n--- 练习 2.2 完成 (图像融合技术概念已介绍) ---")# --- 练习 2.3: 实战:制作全景照片 ---# 这个实战需要多张有重叠的图片。相机位置尽量不变,只旋转。
# 图片示例可以在网上搜索 "image stitching dataset" 或自己拍摄。
# 建议使用两到三张图片进行测试。# 请将你的图片放在一个文件夹中,并在下方指定图片文件列表
image_paths = ['image1.jpg','image2.jpg','image3.jpg',# ... 根据你的图片数量添加
]# 检查图片文件是否存在
loaded_images = []
print("\n--- 实战练习: 制作全景照片 ---")
print("正在加载图片...")
for path in image_paths:try:img = cv2.imread(path)if img is None:raise FileNotFoundError(f"图片文件未找到: {path}")loaded_images.append(img)print(f"成功加载图片: {path}")except FileNotFoundError as e:print(e)print(f"跳过加载图片: {path}")if len(loaded_images) < 2:print("\n错误: 需要至少两张图片进行拼接。")print("请在代码中指定正确的图片文件列表。")run_stitching_실전 = False
else:run_stitching_실전 = Trueif run_stitching_실전:# 1. 创建 Stitcher 对象# cv2.Stitcher_create() 创建默认的 Stitcher# cv2.Stitcher_create(mode) 可以指定拼接模式,如 cv2.STITCHER_PANORAMAstitcher = cv2.Stitcher_create()# stitcher = cv2.Stitcher_create(cv2.STITCHER_PANORAMA) # 显式指定全景模式# 2. 执行图像拼接# stitcher.stitch(images) 返回一个元组:# status: 拼接状态码 (如 cv2.Stitcher_OK 表示成功)# result: 拼接后的图像 (numpy数组)print("正在进行图像拼接...")status, stitched_image = stitcher.stitch(loaded_images)# 3. 处理拼接结果if status == cv2.Stitcher_OK:print("图像拼接成功!")# 显示拼接结果cv2.imshow('Stitched Panorama', stitched_image)# 可以选择性地保存结果# cv2.imwrite('panorama.jpg', stitched_image)# print("拼接结果已保存为 panorama.jpg")elif status == cv2.Stitcher_ERR_NEED_MORE_IMGS:print("错误: 需要更多图片进行拼接。")elif status == cv2.Stitcher_ERR_HOMOGRAPHY_EST_FAIL:print("错误: 单应性矩阵估计失败。匹配点不足或图片间没有足够重叠。")elif status == cv2.Stitcher_ERR_CAMERA_PARAMS_ADJUST_FAIL:print("错误: 相机参数调整失败。")else:print(f"图像拼接失败,状态码: {status}")cv2.waitKey(0)cv2.destroyAllWindows()else:print("\n跳过图像拼接实战,请准备至少两张有重叠的图片。")print("\n--- 实战练习: 制作全景照片 完成 ---")
实战提示:
- 准备图片: 确保你的图片有足够的重叠区域(建议每两张相邻图片之间有 30%-50% 的重叠)。相机尽量保持稳定,只进行旋转拍摄。避免移动相机位置过多,否则单应性模型可能不适用。
- 调整 Stitcher 参数 (进阶):
cv2.Stitcher_create()
可以接受一个模式参数。更高级的调整可以通过访问 Stitcher 对象的各个属性(如特征检测器stitcher.setFeaturesFinder()
, 匹配器stitcher.setMatcher()
, 估计器stitcher.setEstimator()
, 融合器stitcher.setBlender()
等)来进行,但这超出了入门教程的范围。 - 如果拼接失败,检查图片是否满足重叠要求,或者尝试更换特征检测器/匹配器。
3. 深度学习与 OpenCV 结合
深度学习在近年的计算机视觉领域取得了巨大成功,尤其是在图像分类、目标检测、语义分割等方面。OpenCV 提供了 DNN (Deep Neural Network) 模块,允许我们加载和运行各种深度学习框架(如 TensorFlow, Caffe, PyTorch, Darknet 等)训练好的模型。
这意味着你无需深入了解深度学习模型的训练过程,就可以利用现有的优秀模型来解决计算机视觉问题。
3.1 深度学习基础概念 (非常简要)
- 神经网络: 由层层相连的“神经元”组成的计算模型。
- 深度: 指的是神经网络的层数很多。
- 训练: 使用大量带有标签的数据来调整神经网络的参数(权重和偏置),使其能够学习到从输入(如图像像素)到输出(如对象类别、边界框坐标)的映射关系。
- 推理 (Inference): 使用训练好的模型对新的、未知的数据进行预测或分类。
- 卷积神经网络 (CNN): 一种特别适合处理图像的神经网络结构,能够有效地提取图像的层次化特征。
- 预训练模型: 在大型数据集(如 ImageNet)上已经训练好的模型,可以直接使用,或在此基础上进行微调以适应特定任务。
3.2 使用 OpenCV 加载和运行预训练模型
OpenCV 的 DNN 模块简化了加载和运行预训练模型的过程。
Python
import cv2
import numpy as np
import os# --- 练习 3.2: 使用 OpenCV DNN 加载模型 (概念) ---# 1. 获取模型文件
# 你需要模型的架构文件 (如 .cfg, .prototxt) 和权重文件 (如 .weights, .caffemodel, .pb)
# 这些文件通常在模型的官方网站或相关的 GitHub 仓库中提供。
# 例如,对于 YOLO v3,你需要 yolov3.cfg 和 yolov3.weights 文件。# 2. 加载网络模型
# cv2.dnn.readNet(model, config, framework)
# model: 权重文件路径
# config: 架构文件路径 (可选)
# framework: 框架名称 (可选,如 "Caffe", "TensorFlow", "Darknet")
# 如果文件扩展名能确定框架, framework 参数可以省略# 例如加载 Darknet/YOLO 模型
# model_cfg = 'yolov3.cfg'
# model_weights = 'yolov3.weights'
# if os.path.exists(model_cfg) and os.path.exists(model_weights):
# net = cv2.dnn.readNet(model_weights, model_cfg, 'Darknet')
# print("YOLOv3 模型加载成功。")
# else:
# print(f"错误: 未找到模型文件 {model_cfg} 或 {model_weights}")
# net = None # 设置为 None 方便后续检查# 加载 TensorFlow 模型示例 (假设你有 model.pb 和 model.pbtxt)
# model_pb = 'model.pb'
# model_pbtxt = 'model.pbtxt'
# if os.path.exists(model_pb) and os.path.exists(model_pbtxt):
# net = cv2.dnn.readNetFromTensorflow(model_pb, model_pbtxt)
# print("TensorFlow 模型加载成功。")
# else:
# print(f"错误: 未找到模型文件 {model_pb} 或 {model_pbtxt}")
# net = None# 设置首选的后端和设备 (可选,可以优化性能)
# net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) # 如果有CUDA支持,使用GPU
# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
# 或者使用CPU
# net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)# 3. 准备输入图像 (Blob)
# 深度学习模型通常需要特定格式的输入,称为 Blob。
# cv2.dnn.blobFromImage(image, scalefactor, size, mean, swapRB, crop, ddepth)
# image: 输入图像 (numpy数组)
# scalefactor: 缩放因子 (如 1/255.0 将像素值缩放到 0-1 范围)
# size: 模型期望的输入尺寸 (宽度, 高度)
# mean: 从通道值中减去的均值 (如 ImageNet 的均值 [104, 117, 123])
# swapRB: 交换 R 和 B 通道 (OpenCV 默认 BGR 顺序,很多模型期望 RGB)
# crop: 是否裁剪图像
# ddepth: 输出 blob 的深度 (如 cv2.CV_32F)# 例如,对一个图像进行 blob 转换
# image = cv2.imread('your_image.jpg')
# if image is not None:
# blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), swapRB=True, crop=False)
# # size 根据模型的输入要求来定,YOLOv3 常用的有 (320, 320), (416, 416), (608, 608)# 4. 设置输入并进行前向传播 (Inference)
# net.setInput(blob)
# output_layers = net.getUnconnectedOutLayersNames() # 获取输出层名称 (不同模型可能不同)
# outputs = net.forward(output_layers) # 执行推理,获取输出结果print("\n--- 练习 3.2 完成 (OpenCV DNN 加载模型概念已介绍) ---")
# 实际运行将在实战部分展示# --- 练习 3.3: 实战:使用 YOLO 进行对象检测 ---# 这个实战需要下载 YOLO 模型文件和类别名称文件。
# 建议下载较小版本的 YOLO 模型,例如 YOLOv3-tiny。
# 你可以从这里找到链接或文件: https://github.com/pjreddie/darknet (YOLO 官网)
# 或者一些提供了预训练模型的 GitHub 仓库,例如: https://github.com/AlexeyAB/darknet# 请确保你的代码文件同一目录下有以下三个文件:
# 1. 模型的配置文件 (.cfg),例如 yolov3-tiny.cfg
# 2. 模型的权重文件 (.weights),例如 yolov3-tiny.weights
# 3. 类别名称文件 (.names),例如 coco.namesmodel_cfg = 'yolov3-tiny.cfg' # 替换成你的 cfg 文件名
model_weights = 'yolov3-tiny.weights' # 替换成你的 weights 文件名
class_names_file = 'coco.names' # 替换成你的 names 文件名# 检查文件是否存在
if not os.path.exists(model_cfg):print(f"\n错误: 模型配置文件未找到: {model_cfg}")run_yolo_실전 = False
elif not os.path.exists(model_weights):print(f"\n错误: 模型权重文件未找到: {model_weights}")run_yolo_실전 = False
elif not os.path.exists(class_names_file):print(f"\n错误: 类别名称文件未找到: {class_names_file}")run_yolo_실전 = False
else:run_yolo_실전 = Trueif run_yolo_실전:print("\n--- 实战练习: 使用 YOLO 进行对象检测 ---")# 1. 加载网络模型try:net = cv2.dnn.readNet(model_weights, model_cfg, 'Darknet')# 设置首选后端和设备 (可选)# net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)print("YOLOv3-tiny 模型加载成功。")except Exception as e:print(f"错误: 加载模型时发生错误: {e}")print("请检查模型文件是否正确,以及OpenCV是否支持该模型。")run_yolo_실전 = Falseif run_yolo_실전:# 2. 加载类别名称with open(class_names_file, 'r') as f:classes = [line.strip() for line in f.readlines()]# 3. 加载要检测的图像image_path = 'your_object_detection_image.jpg' # 替换成你的测试图片路径 (包含常见物体)try:image = cv2.imread(image_path)if image is None:raise FileNotFoundError(f"测试图片文件未找到: {image_path}")print(f"成功加载测试图像: {image_path}")H, W = image.shape[:2] # 获取图像尺寸except FileNotFoundError as e:print(e)print(f"跳过 YOLO 检测,请检查测试图片路径: {image_path}")run_yolo_실전 = Falseexcept Exception as e:print(f"加载测试图片时发生错误: {e}")run_yolo_실전 = Falseif run_yolo_실전:# 4. 准备输入 Blob# YOLOv3-tiny 期望的输入尺寸是 416x416 (或其他 32 的倍数)# 这里将图像缩放到 416x416,像素值缩放到 0-1 范围,交换 R 和 B 通道blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (416, 416), swapRB=True, crop=False)# 5. 设置输入并进行前向传播 (推理)net.setInput(blob)# 获取 YOLO 输出层的名称# YOLO 有多个输出层,通常通过 getUnconnectedOutLayers 获取output_layers_names = net.getUnconnectedOutLayersNames()# 执行推理print("正在进行推理...")outputs = net.forward(output_layers_names)print("推理完成。")# 6. 后处理: 解析输出结果# YOLO 的输出是一个列表,每个元素是对应输出层的检测结果。# 每个检测结果是一个 numpy 数组,行代表检测到的对象,列包括中心坐标 (x, y), 宽高 (w, h),# 置信度 (objectness score), 以及各个类别的概率。boxes = [] # 存储边界框坐标confidences = [] # 存储置信度classIDs = [] # 存储类别ID# 定义置信度阈值和非极大值抑制阈值confidence_threshold = 0.5 # 只保留置信度高于此值的检测结果nms_threshold = 0.4 # 用于非极大值抑制 (NMS)for output in outputs:for detection in output:# detection 是一个 numpy 数组,前5个元素是 (x, y, w, h, objectness_score),后面是类别概率scores = detection[5:] # 类别概率classID = np.argmax(scores) # 获取概率最高的类别IDconfidence = scores[classID] # 获取对应类别的置信度# 如果置信度高于阈值if confidence > confidence_threshold:# 将边界框的中心坐标和宽高转换为左上角坐标和宽高center_x = int(detection[0] * W) # 乘以原图宽度center_y = int(detection[1] * H) # 乘以原图高度w = int(detection[2] * W)h = int(detection[3] * H)x = int(center_x - w / 2) # 左上角 xy = int(center_y - h / 2) # 左上角 yboxes.append([x, y, w, h])confidences.append(float(confidence))classIDs.append(classID)# 7. 应用非极大值抑制 (NMS)# NMS 用于去除重叠的、冗余的边界框,只保留最佳的检测结果。# cv2.NMSBoxes(boxes, confidences, score_threshold, nms_threshold)# 返回保留下来的边界框的索引indices = cv2.NMSBoxes(boxes, confidences, confidence_threshold, nms_threshold)print(f"检测到 {len(indices)} 个对象 (经过NMS)。")# 8. 在图像上绘制检测结果output_image = image.copy()if len(indices) > 0:for i in indices.flatten(): # indices 是一个多维数组,需要展平x, y, w, h = boxes[i]confidence = confidences[i]classID = classIDs[i]# 获取类别名称和颜色label = f"{classes[classID]}: {confidence:.2f}"# 随机生成颜色 (对于每个类别可以使用固定颜色)color = (0, 255, 0) # 绿色# 绘制边界框cv2.rectangle(output_image, (x, y), (x + w, y + h), color, 2)# 绘制类别标签和置信度cv2.putText(output_image, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)# 9. 显示结果cv2.imshow('Object Detection (YOLO)', output_image)cv2.waitKey(0)cv2.destroyAllWindows()else:print("\n跳过 YOLO 对象检测实战,请确保模型文件 (.cfg, .weights, .names) 已准备就绪。")print("\n--- 实战练习: 使用 YOLO 进行对象检测 完成 ---")
实战提示:
- 获取模型文件: 这是最重要的一步。你需要下载
yolov3-tiny.cfg
,yolov3-tiny.weights
,coco.names
这三个文件,并放在你的代码文件同一目录下。可以尝试从 YOLO 官网或 GitHub 仓库下载。注意文件可能比较大。 - 更换测试图片: 修改
image_path
为你要检测的图片路径。确保图片中包含 COCO 数据集中常见的物体(如人、汽车、猫、狗、椅子等)。 - 调整阈值: 调整
confidence_threshold
和nms_threshold
会影响检测结果。较高的置信度阈值会减少误检,但可能漏掉一些对象;较高的 NMS 阈值会保留更多重叠的框。 - 更快的模型: YOLOv3-tiny 比 YOLOv3 更快,但精度较低。对于实时应用,tiny 模型更实用。
- 其他模型: OpenCV 的 DNN 模块也支持其他框架和模型,例如 SSD, Faster R-CNN 等。你可以尝试加载其他模型文件进行测试。
总结
恭喜你!你已经完成了 OpenCV 教程的第五部分,也是最后一部分的进阶项目实战。我们通过三个项目,将前面学到的基础和中级知识应用到了更复杂的场景:
- 构建了一个简单的人脸识别系统,了解了从检测到识别的基本流程。
- 利用 OpenCV 的
Stitcher
类,轻松实现了图像拼接和全景图制作。 - 通过 OpenCV 的 DNN 模块,学习了如何加载和运行预训练的深度学习模型,成功实现了对象检测。
这些项目只是冰山一角。计算机视觉是一个广阔而活跃的领域,还有很多更高级的技术和应用等待你去探索。
接下来你可以做什么?
- 深入研究: 选择你感兴趣的项目(人脸识别、图像拼接、对象检测等),深入研究其背后的原理和更高级的算法。
- 探索其他模块: OpenCV 还有许多其他模块,如视频分析 (光流、跟踪器)、相机标定、三维重建等。
- 结合其他库: 将 OpenCV 与其他强大的 Python 库结合,如 scikit-image (更多图像处理算法), dlib (高级人脸处理), TensorFlow/PyTorch (构建和训练自己的深度学习模型)。
- 参与开源社区: 查看 OpenCV 的 GitHub 仓库,了解最新的功能和贡献代码。
- 实战项目: 将你学到的知识应用于实际的项目中,解决现实世界的问题。
希望这个系列的教程为你打开了计算机视觉的大门,并激发了你继续学习和探索的兴趣!如果在学习过程中有任何疑问,或者未来遇到问题,都欢迎继续提问。
相关文章:
第五部分:进阶项目实战
在前面的学习中,我们已经掌握了图像和视频的基础操作、增强滤波、特征提取以及一些基础的目标检测方法。现在,我们将综合运用这些知识来构建一些更复杂、更实用的应用项目。 这一部分的项目将结合前面学到的技术,并介绍一些新的概念和工具&a…...
【Linux】记录一个有用PS1
PS1 是用来定义shell提示符的环境变量 下面是一个带有颜色和丰富信息的 Linux PS1 配置示例,包含用户名、主机名、路径、时间、Git 分支和退出状态提示: # 添加到 ~/.bashrc 文件末尾 PS1\[\e[1;32m\]\u\[\e[m\] # 绿色粗体用户名 PS…...
【SpringBoot】基于mybatisPlus的博客管理系统(2)
目录 1.实现用户登录 Jwt令牌 1.引入依赖 2.生成令牌(token) Controller Service Mapper 2.实现强制登录 定义拦截器: 配置拦截器: 1.实现用户登录 在之前的项目登录中,我使用的是Session传递用户信息实现校验…...
免费在Colab运行Qwen3-0.6B——轻量高性能实战
Qwen一直在默默地接连推出新模型。 每个模型都配备了如此强大的功能和高度量化的规模,让人无法忽视。 继今年的QvQ、Qwen2.5-VL和Qwen2.5-Omni之后,Qwen团队现在发布了他们最新的模型系列——Qwen3。 这次他们不是发布一个而是发布了八个不同的模型——参数范围从6亿到235…...
精益数据分析(35/26):SaaS商业模式关键指标解析
精益数据分析(35/26):SaaS商业模式关键指标解析 在创业与数据分析的征程中,我们持续探索不同商业模式的运营奥秘。今天,我们带着共同进步的期望,深入研读《精益数据分析》,聚焦SaaS商业模式&am…...
【论文速读】《Scaling Scaling Laws with Board Games》
论文链接:https://arxiv.org/pdf/2104.03113 《Scaling Scaling Laws with Board Games》:探索棋盘游戏中的扩展规律 摘要 如今,机器学习领域中规模最大的实验所需的资源,超出了仅有几家机构的预算。幸运的是,最近的…...
C++ 与多技术融合的深度实践:从 AI 到硬件的全栈协同
在数字化技术高速发展的今天,C 凭借其卓越的性能优势和底层控制能力,成为连接上层应用与底层硬件的核心纽带。这种独特定位使其在与 AI 深度学习、Python 生态及硬件加速技术的融合中展现出不可替代的价值,构建起从算法实现到硬件优化的全栈技…...
AdaBoost算法的原理及Python实现
一、概述 AdaBoost(Adaptive Boosting,自适应提升)是一种迭代式的集成学习算法,通过不断调整样本权重,提升弱学习器性能,最终集成为一个强学习器。它继承了 Boosting 的基本思想和关键机制,但在…...
无刷马达驱动芯片算法逐步革新着风扇灯行业--其利天下
风扇灯市场热度持续攀升,根据行业数据,风扇灯市场规模从2010年的100亿元增长至2019年的200亿元,年均复合增长率超10%,预计2025年将达30%,借此其利天下有限公司进一步提升了无刷风扇灯驱动方案。 一、性能参数 电压&a…...
数据库系统综合应用与深度实践指南
前言 在当今数据驱动的时代,数据库技术已成为信息系统的核心支柱。从简单的数据存储到复杂的企业级应用,数据库系统支撑着现代社会的方方面面。本文作为一篇综合性的数据库科普文章,旨在为读者提供从基础到进阶的完整知识体系,涵…...
「Unity3D」TextMeshPro使用TMP_InputField实现,输入框高度自动扩展与收缩
先看实现效果: 要实现这个效果,有三个方面的问题需要解决: 第一,输入框的高度扩展,内部子元素会随着锚点,拉伸变形——要解决这个问题,需要将内部元素改变父类,然后增加父类高度&am…...
SAP-ABAP:在SAP系统中,COEP表(成本控制对象行项目表)详解
在SAP系统中,**COEP表(成本控制对象行项目表)**是成本控制(CO)模块的核心数据表之一,主要用于存储与成本核算相关的详细行项目数据。以下是对其作用的详细解析: 一、 COEP表的核心作用 存储成本…...
crashpad 编译
一环境配置 1.1设置系统UTF8编码 1.2vs2017语言环境设置英文包 二.获取depot_tools(此步骤可以跳过 最新工具包已上传下载使用即可) windows下载压缩包,然后放到系统PATH中 下载完以后,基本就是靠depot_tools这个工具集合了&am…...
Windows系统安装Docker(Win10系统升级,然后安装)
有时需要在自己笔记本跑下代码,所以安装Dockers,步骤如下: 1. 升级系统(Windows10专业版或者Windows11) Windows10家庭版装Docker较麻烦,所以我将Win10升级为Win11了(免费)&#x…...
【Fifty Project - D21】
今日完成记录 TimePlan完成情况9:00 - 10:00爬楼梯√12:00 - 14:00Leetcode√14:00 - 15:00《挪威的森林》√ Leetcode 每日一题 今天的每日一题是个easy:给定一个数组,要求统计…...
中央网信办部署开展“清朗·整治AI技术滥用”专项行动
为规范AI服务和应用,促进行业健康有序发展,保障公民合法权益,近日,中央网信办印发通知,在全国范围内部署开展为期3个月的“清朗整治AI技术滥用”专项行动。 中央网信办有关负责人表示,本次专项行动分两个阶…...
《Python实战进阶》 No46:CPython的GIL与多线程优化
Python实战进阶 No46:CPython的GIL与多线程优化 摘要 全局解释器锁(GIL)是CPython的核心机制,它保证了线程安全却限制了多核性能。本节通过concurrent.futures、C扩展优化和多进程架构,实战演示如何突破GIL限制&#…...
BOTA新六维力传感器PixONE:用12维度力矩与运动感测,驱动人形机器人力控未来
在机器人技术日益发展的今天,六维力传感器对于提升机器人感知环境、增强操作精度发挥着重要作用。瑞士BOTA Systems是一家专注于机器人传感器技术的公司,致力于为原始设备制造商提供高性能的传感器解决方案。 PixONE是BOTA推出的一款创新的高精度传感器&…...
《PyTorch documentation》(PyTorch 文档)
PyTorch documentation(PyTorch 文档) PyTorch is an optimized tensor library for deep learning using GPUs and CPUs. (PyTorch是一个优化的张量库,用于使用GPU和CPU进行深度学习。) Features described in this documentation are classified by release status: (此…...
数据库的死锁相关(一)
目录 前言 一、什么死锁 二、产生死锁的必要条件 三、死锁发生的具体位置和场景 1. 数据行级别死锁(最常见) 2. 表级别死锁 3. 索引间隙锁死锁(InnoDB特有) 4. 外键约束死锁 5. 元数据锁死锁 6. 内存中的锁结构死锁 7.…...
数据编码(Encoding)
对数据做编码可以减少存储和 I/O开销,常见的技术比如 Dictionary Encoding,Run-Length Encoding,Bitpacking,Delta Encoding,Frame-of-Reference等。 本篇文章对这些编码方案进行介绍,举例说明,最后总结各种encoding的适用场景。 一、Dictionary Encoding(字典编码)…...
Wartales 战争传说 [DLC 解锁] [Steam] [Windows SteamOS]
Wartales 战争传说 [DLC 解锁] [Steam] [Windows & SteamOS] DLC 版本 至最新全部 DLC 后续可能无法及时更新文章,具体最新版本见下载文件说明 DLC 解锁列表(仅供参考) 《战争传说》 - Pirates of Belerion 《战争传说》 - The Tavern …...
决策树在电信客户流失分析中的实战应用
在当今数据驱动的时代,数据分析和机器学习技术在各行业的应用愈发广泛。电信行业面临着激烈的竞争,客户流失问题成为影响企业发展的关键因素之一。如何准确预测客户是否会流失,并采取相应措施挽留客户,是电信企业关注的重点。决策…...
滚珠丝杆怎么选型?
滚珠丝杆的选型需要考虑多个因素,包括应用需求、性能参数、环境因素等,以确保选型的准确性和合理性。 1、负载:确定设备运行时滚珠丝杆需要承受的静载荷和动载荷,包括轴向载荷和径向载荷,根据实际工作情况计算出最大负…...
HTN77A0原理图提供聚能芯半导体禾润一级代理技术支持免费送样
在电源管理需求日益严苛的当下,禾润 HTN77A0 以卓越性能脱颖而出。它不仅适配多种应用场景,还兼具高效节能与稳定输出,为设备供能带来革新体验。 禾润 HTN77A0 同步降压变换器,凭借5V~130V 超宽输入电压范围,打破传统供…...
linux中sigint和sigterm的区别
SIGINT 和 SIGTERM 是在 Unix 及类 Unix 系统(包括 Linux)中用于进程间通信的信号,它们都可以用于请求进程终止,区别如下: 1、信号编号与定义 在信号机制里,每个信号都有对应的编号,这便于系统…...
errorno 和WSAGetlasterror的区别
errno 和 WSAGetLastError 是用于获取错误代码的机制,但它们应用于不同的编程场景,下面为你详细介绍二者的区别: 应用场景 errno:它是 C 和 C 等编程语言里用于表示系统调用和库函数错误的全局变量。在 Unix、Linux 等类 Unix 系…...
《操作系统真象还原》第十一章——用户进程
文章目录 前言为什么要有TSSTSS简介TSS描述符和TSS结构现代操作系统采用的任务切换方式 定义并初始化TSS修改global.h编写tss.c测试 实现用户进程实现用户进程的原理维护虚拟地址空间,修改thread.h为进程创建页表和3特权级栈,修改memory.c进入特权级3用户…...
第 11 届蓝桥杯 C++ 青少组中 / 高级组省赛 2020 年真题答和案解析
一、选择题 第 1 题 单选题 题目:表达式 ‘6’ - ‘1’ 的值是 ( ) A. 整数 5 B. 字符 5 C. 表达式不合法 D. 字符 6 答案:A 解析:在 C++ 中,字符常量以 ASCII 码形式存储。6 的 ASCII 码为 54,1 的 ASCII 码为 49,二者相减结果为 5,是整数类型,因此选 A。 第 2 题 …...
ES搜索知识
GET /categories/1/10?name手机 // 按名称过滤 GET /categories/1/10?type电子产品 // 按类型过滤 GET /categories/1/10?name手机&type电子产品 // 组合过滤 查询参数 ApiOperation(value "获取商品分类分页列表")GetMapping("{page}/{limit}")…...
Java高阶程序员学习计划(详细到天,需有一定Java基础)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息文章目录 Java高阶程序员学习计划(详细到天,需有一定Java基础)第一阶段(30天)Java基础:Java生态工具链:设计模式与编码规范:第二阶段(15天…...
SALOME源码分析: SMESH模块
本文分析SALOME GEOM模块。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 一、核心组件 1.1 SMESHGUI 二、关键流程 网络资料 SALOME: Introduction to MESH Modulehttps://docs.salome-platform…...
提高程序灵活性和效率的利器:Natasha动态编译库【.Net】
从零学习构建一个完整的系统 今天推荐一个针对C#动态编译库,动态编译的使用场景有很多: 1、动态代码生成:可以根据用户的输入或者系统配置动态执行C#代码,比如很多Web在线编译器,就是这个原理; 2、代码反…...
Cangjie Magic在医疗领域的应用:智能体技术如何重塑医疗数字化
文章目录 1. Cangjie Magic是什么?有什么优势?2. Cangjie Magic与Python的区别与优势对比技术特性对比医疗场景案例对比案例1:电子病历自然语言处理案例2:ICU实时监护系统 3. Cangjie Magic的学习成本与性价比学习门槛性价比优势 …...
MySQL基础关键_002_DQL(一)
目 录 一、初始化 二、简单查询 1.部分语法规则 2.查询一个字段 (1)查询员工编号 (2)查询员工姓名 3.查询多个字段 (1)查询员工编号、姓名 (2)查询部门编号、名称、位置 …...
从高端制造到民生场景:天元轻量化软件的“破局”之路
近期,清华大学航空发动机研究院(以下简称“清华航发院”)正式引入天元轻量化软件,用于其相关设计与3D可视化研究工作。 作为国内领先的3D轻量化解决方案提供商,天元轻量化软件一直致力于为各行业提供高效、灵活、安全…...
本地部署大模型的方式有哪些
本地部署大模型的方式主要分为 应用部署 和 源码部署 两大类,具体分类及特点如下: 一、应用部署(适合新手) 特点:无需编程基础,通过厂商提供的工具直接安装使用,支持图形化界面和命令行操作&am…...
libevent详解
目录 一、安装libevent库 二、libevent 三、基于 libevent 实现信号处理与定时任务 四、基于 libevent 的事件驱动 TCP 服务端代码 一、安装libevent库 sudo su apt install libevent-dev 二、libevent libevent 是一个轻量级网络i/o库,i/o框架库,…...
Solon Cloud Gateway 补充
说明 在「使用 Solon Cloud Gateway 替换Spring Cloud Gateway 」的文章中,有评论说不知道响应式。当时看的是 Solon Cloud Gateway 使用响应式接口,由 Solon-Rx 来实现,是基于 reactive-streams 封装的 RxJava 极简版。目前仅一个接口 Com…...
海外社交软件技术深潜:实时互动系统与边缘计算的极限优化
一、毫秒级延迟之战:下一代实时通信架构 1.1 全球实时消息投递体系设计 图表 代码 性能基准测试(跨大西洋传输): 协议/算法组合 平均延迟 99分位延迟 丢包恢复率 WebSocketTLSBBRv2 142ms 298ms 78% QUIC自定义CC 112ms 201ms 92%…...
直播美颜SDK是什么?跨平台美颜SDK开发与接入全解析
当下,越来越多的直播平台、短视频App、社交娱乐应用,开始在画面美化方面加大投入。本文将围绕直播美颜SDK是什么、其背后的核心技术、如何实现跨平台开发、以及接入流程等关键问题,为你全面解析这一技术热点。 一、直播美颜SDK到底是什么&am…...
自学S32k144(18)————芯片锁死问题及成功解锁流程
1.锁死原因 温度过高flash异常操作静电等电压异常问题。。。。 本人出现情况:之前开发板不知什么原因,发生短路,重新置换芯片后,发现芯片在S32DS中无法正常烧录 判断可能是由于焊接时温度过高导致锁死。需解锁芯片。 2.解决方法…...
【免费数据】2000-2020年中国4km分辨率逐日气象栅格数据(含9个气象变量)
逐日气象数据是在很多研究中都会用到的数据,例如验证气候模拟、分析陆地生态系统变化以及识别气候变化下的极端天气条件等研究,尤其是高精度的逐日气象数据对于研究者来说更为常用。 本次我们为大家带来的是2000-2020年中国4km分辨率逐日气象栅格数据&a…...
Android Compose 无网络状态处理全指南:从基础到高级实践
Android Compose 无网络状态界面处理全方案 引言 在移动应用开发中,网络连接不稳定是常见场景。优雅地处理无网络状态能显著提升用户体验。Jetpack Compose 提供了强大的工具来实现各种网络状态下的界面展示。本文将全面介绍在 Compose 中处理无网络状态的多种方案…...
网络规划和设计
1.结构化综合布线系统包括建筑物综合布线系统PDS,智能大夏布线系统IBS和工业布线系统IDS 2.GB 50311-2016综合布线系统工程设计规范 GB/T 50312-2016综合布线系统工程验收规范 3.结构化布线系统分为6个子系统: 工作区子系统;水平布线子系…...
Learning vtkjs之ImageMarchingSquares
体积 等值线处理 介绍 vtkImageMarchingSquares - 对图像(或来自体积的切片)进行等值线处理 给定一个指定的等值,使用Marching Squares算法(3D Marching Cubes算法的2D版本)生成等值线。 效果 自己增加了两个小球&…...
前端跨域问题详解:原因、解决方案与最佳实践
引言 在现代Web开发中,跨域问题是前端工程师几乎每天都会遇到的挑战。随着前后端分离架构的普及和微服务的发展,跨域请求变得愈发常见。本文将深入探讨跨域问题的本质、各种解决方案以及在实际开发中的最佳实践。 一、什么是跨域问题? 1.1…...
第五届图像、视觉与智能系统国际会议(ICIVIS 2025)参会通知
大会官网: http://www.icivis.net/ 官方邮箱:icivis163.com 会议地点:杭州师范大学仓前校区(余杭塘路2318号) 会议时间:2025年5月23日-5月25日 主办单位:杭州师范大学 1.一般会员注册 提交注册表以后…...
PCB设计工艺规范(二)基本布局要求
基本布局要求 1.PCBA加工工序2.对器件以及PCB布局要求 资料来自网络,仅供学习使用。 1.PCBA加工工序 制成板的元件布局应保证制成板的加工工序合理,以便于提高制成板加工效率和直通率。 PCB 布局选用的加工流程应使加工效率最高。 常用 PCBA 的6种主流…...
SWIG 和 JNA / JNI 等 C 接口封装工具及进行 C 接口的封装
SWIG 相关 SWIG 是什么 SWIG 是一个软件开发工具,是一个 封装 C/C++ 动态库供其他编程语言调用的神器。 使用它可以简化不同语言与与 C/C++ 语言的交互。简单点说,SWIG 是一个编译器,它以 C/C++的声明为输入,创建从其他语言包括常见的脚本语言如 Javascript、Perl、PHP、…...