说几个实际能用的。

先装环境

```bash

pip install opencv-python opencv-contrib-python numpy

```

opencv-python 是基础库,opencv-contrib-python 带额外模块(包括我们要用的跟踪器)。如果只需要基础功能,只装第一个就够了。

基于颜色的目标检测

最简单的方法,不需要训练模型,适合颜色特征明显的场景。

```python

import cv2

import numpy as np

def detect_by_color(frame, lower_color, upper_color):

    """按颜色范围检测目标,返回轮廓列表"""

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    mask = cv2.inRange(hsv, lower_color, upper_color)

    # 去噪

    mask = cv2.erode(mask, None, iterations=2)

    mask = cv2.dilate(mask, None, iterations=2)

    # 找轮廓

    contours, _ = cv2.findContours(

        mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE

    )

    return contours

def draw_detection(frame, contours, min_area=500):

    """在图上画出检测到的目标"""

    for cnt in contours:

        area = cv2.contourArea(cnt)

        if area < min_area:

            continue

        x, y, w, h = cv2.boundingRect(cnt)

        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

        cv2.putText(

            frame, f"obj {area:.0f}",

            (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,

            0.5, (0, 255, 0), 1,

        )

    return frame

if __name__ == "__main__":

    # 蓝色范围 (HSV)

    lower_blue = np.array([100, 50, 50])

    upper_blue = np.array([130, 255, 255])

    cap = cv2.VideoCapture(0)

    while True:

        ret, frame = cap.read()

        if not ret:

            break

        contours = detect_by_color(frame, lower_blue, upper_blue)

        frame = draw_detection(frame, contours)

        cv2.imshow("Color Detection", frame)

        if cv2.waitKey(1) & 0xFF == ord("q"):

            break

    cap.release()

    cv2.destroyAllWindows()

```

HSV 颜色范围需要根据实际场景调。蓝色大概在 100-130 度,绿色 40-80,红色有两个区间 0-10 和 160-180。

颜色检测的局限很明显:光照一变,颜色就偏了。室内用还行,室外基本靠不住。

用预训练模型做检测

OpenCV 的 DNN 模块可以直接加载训练好的模型,不需要自己训练。

```python

import cv2

import numpy as np

class ObjectDetector:

    def __init__(self, model_path: str, config_path: str,

                 labels_path: str, confidence_threshold: float = 0.5):

        self.net = cv2.dnn.readNet(model_path, config_path)

        self.confidence_threshold = confidence_threshold

        with open(labels_path) as f:

            self.labels = [line.strip() for line in f.readlines()]

    def detect(self, frame):

        """对一帧图像做目标检测,返回 (label, confidence, box) 列表"""

        h, w = frame.shape[:2]

        # 转成模型输入格式

        blob = cv2.dnn.blobFromImage(

            frame, 1 / 255.0, (416, 416),

            swapRB=True, crop=False

        )

        self.net.setInput(blob)

        # 前向传播

        layer_names = self.net.getUnconnectedOutLayersNames()

        outputs = self.net.forward(layer_names)

        results = []

        for output in outputs:

            for detection in output:

                scores = detection[5:]

                class_id = np.argmax(scores)

                confidence = scores[class_id]

                if confidence < self.confidence_threshold:

                    continue

                center_x = int(detection[0] * w)

                center_y = int(detection[1] * h)

                box_w = int(detection[2] * w)

                box_h = int(detection[3] * h)

                x = int(center_x - box_w / 2)

                y = int(center_y - box_h / 2)

                results.append((

                    self.labels[class_id],

                    float(confidence),

                    (x, y, box_w, box_h),

                ))

        return results

def draw_results(frame, results):

    """在图像上绘制检测结果"""

    for label, confidence, (x, y, w, h) in results:

        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

        text = f"{label} {confidence:.2f}"

        cv2.putText(

            frame, text, (x, y - 5),

            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1,

        )

    return frame

if __name__ == "__main__":

    detector = ObjectDetector(

        model_path="yolov3.weights",

        config_path="yolov3.cfg",

        labels_path="coco.names",

    )

    cap = cv2.VideoCapture(0)

    while True:

        ret, frame = cap.read()

        if not ret:

            break

        results = detector.detect(frame)

        frame = draw_results(frame, results)

        cv2.imshow("Detection", frame)

        if cv2.waitKey(1) & 0xFF == ord("q"):

            break

    cap.release()

    cv2.destroyAllWindows()

```

YOLOv3 的权重文件和配置文件需要单独下载,大约 240MB。下载地址:

```

wget https://pjreddie.com/media/files/yolov3.weights

wget https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg

wget https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names

```

放到项目目录下就能跑。模型能识别 80 类常见物体——人、车、手机、猫狗这些。

跟踪移动目标

如果摄像头固定不动,检测移动的目标用背景减法更高效。

```python

import cv2

class MotionDetector:

    def __init__(self, min_area: int = 2000):

        self.bg_subtractor = cv2.createBackgroundSubtractorMOG2()

        self.min_area = min_area

    def detect(self, frame):

        """检测运动区域,返回轮廓列表"""

        mask = self.bg_subtractor.apply(frame)

        # 去噪

        mask = cv2.erode(mask, None, iterations=2)

        mask = cv2.dilate(mask, None, iterations=2)

        contours, _ = cv2.findContours(

            mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE

        )

        return [c for c in contours if cv2.contourArea(c) > self.min_area]

if __name__ == "__main__":

    detector = MotionDetector(min_area=3000)

    cap = cv2.VideoCapture(0)

    print("摄像头已开启,按 q 退出")

    while True:

        ret, frame = cap.read()

        if not ret:

            break

        motions = detector.detect(frame)

        for cnt in motions:

            x, y, w, h = cv2.boundingRect(cnt)

            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)

            cv2.putText(

                frame, "MOTION",

                (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,

                0.6, (0, 0, 255), 2,

            )

        cv2.putText(

            frame, f"Moving objects: {len(motions)}",

            (10, 30), cv2.FONT_HERSHEY_SIMPLEX,

            0.7, (255, 255, 255), 2,

        )

        cv2.imshow("Motion Detection", frame)

        if cv2.waitKey(1) & 0xFF == ord("q"):

            break

    cap.release()

    cv2.destroyAllWindows()

```

背减减法对光照突变很敏感——开灯关灯会触发大量误报。适合室内光线稳定的监控场景。

目标跟踪

检测到目标之后,如果要对单个目标持续跟踪,用跟踪器比每帧都检测更省资源。

```python

import cv2

def main():

    cap = cv2.VideoCapture(0)

    tracker = cv2.TrackerCSRT_create()

    tracking = False

    print("按 s 框选目标开始跟踪,按 q 退出")

    while True:

        ret, frame = cap.read()

        if not ret:

            break

        if tracking:

            success, box = tracker.update(frame)

            if success:

                x, y, w, h = [int(v) for v in box]

                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

                cv2.putText(

                    frame, "TRACKING",

                    (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,

                    0.6, (0, 255, 0), 2,

                )

            else:

                cv2.putText(

                    frame, "LOST",

                    (50, 50), cv2.FONT_HERSHEY_SIMPLEX,

                    1, (0, 0, 255), 2,

                )

        else:

            cv2.putText(

                frame, "Press 's' to select target",

                (50, 50), cv2.FONT_HERSHEY_SIMPLEX,

                0.7, (255, 255, 255), 2,

            )

        cv2.imshow("Tracker", frame)

        key = cv2.waitKey(1) & 0xFF

        if key == ord("s") and not tracking:

            # 选择目标区域

            box = cv2.selectROI("Tracker", frame, False)

            if box[2] > 0 and box[3] > 0:

                tracker.init(frame, box)

                tracking = True

                cv2.destroyWindow("Tracker")

        elif key == ord("q"):

            break

    cap.release()

    cv2.destroyAllWindows()

if __name__ == "__main__":

    main()

```

OpenCV 自带的 TrackerCSRT 准确率高但慢。如果对速度有要求,换 TrackerKCF,速度快但容易跟丢。按 s 键框选目标,之后由跟踪器自动跟随。

图片批量处理

有时候不是实时检测,而是批量处理文件夹里的图片。

```python

import cv2

from pathlib import Path

def batch_detect(input_dir: str, output_dir: str, detector):

    """批量检测文件夹中的图片"""

    Path(output_dir).mkdir(exist_ok=True)

    image_exts = {".jpg", ".jpeg", ".png", ".bmp"}

    for filepath in Path(input_dir).iterdir():

        if filepath.suffix.lower() not in image_exts:

            continue

        img = cv2.imread(str(filepath))

        if img is None:

            print(f"无法读取: {filepath.name}")

            continue

        results = detector.detect(img)

        img = draw_results(img, results)

        output_path = Path(output_dir) / filepath.name

        cv2.imwrite(str(output_path), img)

        print(f"{filepath.name}: 检测到 {len(results)} 个目标")

if __name__ == "__main__":

    detector = ObjectDetector("yolov3.weights", "yolov3.cfg", "coco.names")

    batch_detect("./input_images", "./output_images", detector)

```

几个踩过的坑

1. 摄像头打不开。cap = cv2.VideoCapture(0) 里的 0 是摄像头编号。笔记本内置摄像头一般用 0,外接 USB 摄像头可能用 1 或 2。有时候被其他程序占用了也会打不开,先关掉微信/QQ 的视频功能再试。

2. DNN 模型加载慢。YOLOv3 的权重文件 240MB,每次启动加载要几秒。频繁重启的话考虑把 detector 对象做成全局的,只加载一次。

3. 实时检测很吃 CPU。笔记本上 YOLOv3 跑实时视频流大概 5-10 帧,换 YOLOv3-tiny(更小的模型)能到 20 帧以上,准确率下降一些但够用。

4. 颜色检测受光照影响大。室内可以用,室外基本不行。如果场景固定,可以在不同时段多采样几个颜色范围取平均。

5. 中文路径问题。OpenCV 的 imread 不支持中文路径。路径里有中文,读出来是 None。要么全用英文路径,要么用 numpy 绕一下:

```python

import numpy as np

def imread_chinese(path):

    """支持中文路径的 cv2.imread"""

    return cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)

```

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐