第五部分:进阶项目实战

在前面的学习中,我们已经掌握了图像和视频的基础操作、增强滤波、特征提取以及一些基础的目标检测方法。现在,我们将综合运用这些知识来构建一些更复杂、更实用的应用项目。

这一部分的项目将结合前面学到的技术,并介绍一些新的概念和工具,包括:

  1. 人脸识别系统: 从识别到识别"谁"的脸。
  2. 图像拼接与全景图制作: 将多张有重叠的图片合成为一张大图。
  3. 深度学习与 OpenCV 结合: 利用 OpenCV 的 DNN 模块运行深度学习模型进行目标检测。

请注意,这些项目本身都是复杂领域,本部分将提供入门级别的实现,让你了解其基本流程和关键技术。每个实战项目都需要你准备相应的图片或模型文件。


OpenCV 进阶项目实战 (第五部分)

欢迎来到 OpenCV 教程的第五部分!现在你已经具备了坚实的 OpenCV 基础知识。是时候将这些知识融会贯通,应用于一些更高级、更有趣的计算机视觉项目了。

本部分将带你构建三个常见的计算机视觉应用:一个简单的人脸识别系统、一个图像拼接工具和一个基于深度学习的对象检测应用。这些项目将帮助你理解如何将不同的 OpenCV 功能组合起来解决实际问题。

1. 人脸识别系统

人脸识别是一个重要的计算机视觉任务,广泛应用于安全、身份验证、社交媒体等领域。人脸识别与我们之前学过的人脸检测不同,检测只是找到人脸的位置,而识别是确定这张脸属于"谁"。

一个典型的人脸识别系统流程包括:

  1. 人脸检测: 在图像中找到人脸的位置(边界框)。
  2. 人脸对齐 (Alignment): 将检测到的人脸区域进行标准化处理,例如旋转、缩放,使得眼睛、鼻子等关键点位于大致相同的位置,以减少姿势、角度等变化的影响。
  3. 特征提取: 从对齐后的人脸图像中提取出能够唯一代表人脸身份的特征(人脸描述符)。
  4. 特征匹配/比较: 将当前人脸的特征与一个已知人脸库中的特征进行比较,找到最相似的特征,从而确定身份。

我们将构建一个基于 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_실전 = True


if run_face_recognition_실전 and face_cascade is not None and recognizer is not None:
    print("\n--- 实战练习: 简单人脸识别应用 ---")

    images = [] # 用于存储训练图像
    labels = [] # 用于存储对应的标签 (人物ID)
    label_map = {} # 用于存储人物ID到姓名的映射
    current_id = 0

    print("正在加载和预处理训练数据...")
    # 遍历数据集文件夹
    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_id
                current_id += 1
            label = 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)) # 调整大小到100x100
                        images.append(face_img)
                        labels.append(label)
                        # print(f"已处理 {image_path} 提取人脸,标签: {label}")
                    else:
                        # print(f"警告: 未在 {image_path} 中检测到人脸,跳过。")
                        pass

                except 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 = name
                        break

                # 根据置信度判断是否识别成功
                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. 图像拼接与全景图制作

图像拼接是将多张有重叠区域的图像合并成一张视野更宽的图像(如全景图)的技术。

基本流程:

  1. 特征提取: 在每张图像中找到特征点(如 ORB, SIFT, SURF)。
  2. 特征匹配: 找到不同图像中对应于同一场景点的特征点对。
  3. 估计变换: 根据匹配的特征点对,计算图像之间的几何变换关系(如单应性变换 Homography)。如果场景是平面的,或者相机只旋转,单应性变换适用;如果场景有深度变化且相机移动,则需要更复杂的变换(如基本矩阵Fundamental Matrix)。
  4. 图像校正/扭曲 (Warping): 根据计算出的变换,将图像进行校正或扭曲,使其在同一个平面或透视下对齐。
  5. 图像融合 (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_실전 = True


if run_stitching_실전:
    # 1. 创建 Stitcher 对象
    # cv2.Stitcher_create() 创建默认的 Stitcher
    # cv2.Stitcher_create(mode) 可以指定拼接模式,如 cv2.STITCHER_PANORAMA
    stitcher = 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.names

model_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_실전 = True


if 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_실전 = False


if 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_실전 = False
    except Exception as e:
         print(f"加载测试图片时发生错误: {e}")
         run_yolo_실전 = False


if 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) # 获取概率最高的类别ID
            confidence = 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) # 左上角 x
                y = int(center_y - h / 2) # 左上角 y

                boxes.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_thresholdnms_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 仓库,了解最新的功能和贡献代码。
  • 实战项目: 将你学到的知识应用于实际的项目中,解决现实世界的问题。

希望这个系列的教程为你打开了计算机视觉的大门,并激发了你继续学习和探索的兴趣!如果在学习过程中有任何疑问,或者未来遇到问题,都欢迎继续提问。

相关推荐
老刘说AI4 分钟前
Coze:从入门到精通
人工智能·低代码·语言模型·开放原子·知识图谱·持续部署
qq_白羊座6 分钟前
Langchain、Cursor、python的关系
开发语言·python·langchain
小陈的进阶之路6 分钟前
接口Mock测试
python·mock
kiku18188 分钟前
Python网络编程
开发语言·网络·python
IT观测11 分钟前
选高低温环境试验箱,品牌、生产商、厂家哪个维度更可靠?
大数据·人工智能
isNotNullX12 分钟前
BI如何落地?BI平台如何搭建?
大数据·数据库·人工智能
新新学长搞科研13 分钟前
【多所权威高校支持】第五届新能源系统与电力工程国际学术会议(NESP 2026)
运维·网络·人工智能·自动化·能源·信号处理·新能源
zncxCOS14 分钟前
【ETestDEV5教程30】ICD操作之信号组操作
python·测试工具·测试用例·集成测试
枫叶林FYL14 分钟前
第八章 长上下文建模与位置编码优化 (Long Context Modeling) 8.1 位置编码外推技术
人工智能
砍材农夫14 分钟前
spring-ai 第八模型介绍-图像模型
java·人工智能·spring