前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用OpenCV和YOLOv8制作目标检测器(附源码)

使用OpenCV和YOLOv8制作目标检测器(附源码)

作者头像
小F
发布2023-09-19 08:14:41
1K1
发布2023-09-19 08:14:41
举报

YOLO(You Only Look Once)是由Joseph Redmon和Ali开发的一种对象检测和图像分割模型。

YOLO的第一个版本于2015年发布,由于其高速度和准确性,瞬间得到了广大AI爱好者的喜爱。

Ultralytics YOLOv8则是一款前沿、最先进(SOTA)的模型,基于先前YOLO版本的成功,引入了新功能和改进,进一步提升性能和灵活性。

YOLOv8设计快速、准确且易于使用,使其成为各种物体检测与跟踪、实例分割、图像分类和姿态估计任务的绝佳选择。

项目地址:

https://github.com/ultralytics/ultralytics

其中官方提供了示例,通过Python代码即可实现YOLOv8对象检测算法模型,使用预训练模型来检测我们的目标。

而且对电脑需求也不高,CPU就能运行代码

今天小F就给大家介绍三个使用YOLOv8制作的检测器,非常实用。

/ 01 /

行人检测器

使用YOLOv8精确检测行人。

并且设定行人计数区域,实现实时计算区域内行人的数量。

这项技术可以制作一个,人员密集场所客流量统计监测系统。

非常适合火车站、地铁站、机场等场景,实现客流安全管理。

使用到Python版本即相关Python库。

代码语言:javascript
复制
Python 3.9.7
supervision 0.2.0
ultralytics 8.0.154
torchvision 0.13.0
opencv-contrib-python 4.8.0.74
opencv-python 4.8.0.74

其中OpenCV库大家应该都不陌生。

supervision库是一个可重复使用的计算机视觉工具,帮助处理数据集、检测结果等工作。

可以降低我们学习计算机视觉的难度~

项目地址:

https://github.com/roboflow/supervision

接下来就先看一下全区域的行人检测代码吧!

代码语言:javascript
复制
import numpy as np
import supervision as sv
from ultralytics import YOLO


class CountObject():

    def __init__(self, input_video_path, output_video_path) -> None:
        # 加载YOLOv8模型
        self.model = YOLO('yolov8s.pt')

        # 输入视频, 输出视频
        self.input_video_path = input_video_path
        self.output_video_path = output_video_path

        # 视频信息
        self.video_info = sv.VideoInfo.from_video_path(input_video_path)

        # 检测框属性
        self.box_annotator = sv.BoxAnnotator(thickness=4, text_thickness=4, text_scale=2)

    def process_frame(self, frame: np.ndarray, _) -> np.ndarray:
        # 检测
        results = self.model(frame, imgsz=1280)[0]
        detections = sv.Detections.from_yolov8(results)
        detections = detections[detections.class_id == 0]

        # 绘制检测框
        box_annotator = sv.BoxAnnotator(thickness=4, text_thickness=4, text_scale=2)
        labels = [f"{self.model.names[class_id]} {confidence:0.2f}" for _, confidence, class_id, _ in detections]
        frame = box_annotator.annotate(scene=frame, detections=detections, labels=labels)

        return frame

    def process_video(self):
        # 处理视频
        sv.process_video(source_path=self.input_video_path, target_path=self.output_video_path,
                         callback=self.process_frame)


if __name__ == "__main__":
    obj = CountObject('demo.mp4', 'single.mp4')
    obj.process_video()

运行代码,结果如下。

发现检测效果还不错,目标的置信度都挺高的。

进行区域划分,编写算法,判断不同区域内的人员数量。

代码语言:javascript
复制
import numpy as np
import supervision as sv
from ultralytics import YOLO


class CountObject():

    def __init__(self, input_video_path, output_video_path) -> None:
        # 加载YOLOv8模型
        self.model = YOLO('yolov8s.pt')
        # 设置颜色
        self.colors = sv.ColorPalette.default()

        # 输入视频, 输出视频
        self.input_video_path = input_video_path
        self.output_video_path = output_video_path

        # 多边形区域
        self.polygons = [
            np.array([
                [540, 985],
                [1620, 985],
                [2160, 1920],
                [1620, 2855],
                [540, 2855],
                [0, 1920]
            ], np.int32),
            np.array([
                [0, 1920],
                [540, 985],
                [0, 0]
            ], np.int32),
            np.array([
                [1620, 985],
                [2160, 1920],
                [2160, 0]
            ], np.int32),
            np.array([
                [540, 985],
                [0, 0],
                [2160, 0],
                [1620, 985]
            ], np.int32),
            np.array([
                [0, 1920],
                [0, 3840],
                [540, 2855]
            ], np.int32),
            np.array([
                [2160, 1920],
                [1620, 2855],
                [2160, 3840]
            ], np.int32),
            np.array([
                [1620, 2855],
                [540, 2855],
                [0, 3840],
                [2160, 3840]
            ], np.int32)
        ]

        # 视频信息
        self.video_info = sv.VideoInfo.from_video_path(input_video_path)

        # 多边形区域
        self.zones = [
            sv.PolygonZone(
                polygon=polygon,
                frame_resolution_wh=self.video_info.resolution_wh
            )
            for polygon
            in self.polygons
        ]

        # 区域计数数量标识属性
        self.zone_annotators = [
            sv.PolygonZoneAnnotator(
                zone=zone,
                color=self.colors.by_idx(index),
                thickness=6,
                text_thickness=8,
                text_scale=4
            )
            for index, zone
            in enumerate(self.zones)
        ]

        # 检测框绘制属性
        self.box_annotators = [
            sv.BoxAnnotator(
                color=self.colors.by_idx(index),
                thickness=4,
                text_thickness=4,
                text_scale=2
            )
            for index
            in range(len(self.polygons))
        ]

    def process_frame(self, frame: np.ndarray, i) -> np.ndarray:
        # 检测
        results = self.model(frame, imgsz=1280)[0]
        detections = sv.Detections.from_yolov8(results)
        detections = detections[(detections.class_id == 0) & (detections.confidence > 0.5)]

        # 绘制区域、区域数量、检测框
        for zone, zone_annotator, box_annotator in zip(self.zones, self.zone_annotators, self.box_annotators):
            mask = zone.trigger(detections=detections)
            detections_filtered = detections[mask]
            frame = box_annotator.annotate(scene=frame, detections=detections_filtered, skip_label=True)
            frame = zone_annotator.annotate(scene=frame)

        return frame

    def process_video(self):
        # 处理视频
        sv.process_video(source_path=self.input_video_path, target_path=self.output_video_path,
                         callback=self.process_frame)


if __name__ == "__main__":
    obj = CountObject('demo.mp4', 'multiple.mp4')
    obj.process_video()

运行代码,结果如下。

这样每个区域的行人就计算展示出来了。

/ 02 /

小狗检测器

小狗检测器,防止小狗跳到沙发上,留下一团毛~

非常适合想要训练宠物,或者保护家具的宠物主人。

实现实时检测小狗,并且在小狗爬上沙发后触发报警。

其中报警推送可以设置成微信or短信,也是可以实现的。

具体代码如下。

代码语言:javascript
复制
import io
import os
import cv2
import telebot
import threading
import numpy as np
from PIL import Image
import pygame as pygame
from ultralytics import YOLO
from datetime import datetime, timedelta

# 设置报警机器人, 可以使用微信替代
# telegram_key = ""
# chat_id = ""
# bot = telebot.TeleBot(telegram_key)
# shared_frame = None

# 发消息
# def send_message(chat_id, message):
#     bot.send_message(chat_id, message)
#

# 发图片
# def send_photo(chat_id, frame):
#     img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
#     img = Image.fromarray(img_rgb)
#
#     bio = io.BytesIO()
#     bio.name = 'image.jpeg'
#     img.save(bio, 'JPEG')
#     bio.seek(0)
#
#     bot.send_photo(chat_id, bio)

# 监听
# def telegram_listener():
#     @bot.message_handler(func=lambda message: True)
#     def echo_all(message):
#         if message.text == "update":
#             global shared_frame
#             if shared_frame is not None:
#                 send_photo(chat_id, shared_frame)
#     bot.polling()

# 报警机器人
# telegram_thread = threading.Thread(target=telegram_listener)
# telegram_thread.start()

# 时间计算
last_sent_time = datetime.now() - timedelta(seconds=15)
# 加载模型
model = YOLO("yolov8n.pt")
# 检测类别
classes = [
    'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck',
    'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench',
    'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra',
    'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis',
    'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard',
    'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon',
    'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog',
    'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table',
    'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
    'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
    'teddy bear', 'hair drier', 'toothbrush'
]

# 初始化pygame, 加载报警音乐
pygame.init()
pygame.mixer.init()
alarm_played = False
pygame.mixer.music.load(os.path.abspath('alarm.mp3'))

COLORS = np.random.uniform(0, 255, size=(len(classes), 3))

# 加载视频
cap = cv2.VideoCapture('test_video1.mp4')
fps_camera = cap.get(cv2.CAP_PROP_FPS)
target_fps = 10
n = int(fps_camera / target_fps)
frame_counter, not_detected = 0, 0

while True:
    ret, frame = cap.read()
    if not ret:
        break
    shared_frame = frame

    if frame_counter % n == 0:
        # 检测
        outs = model(frame, task='detect', iou=0.2, conf=0.3, show=True, save_conf=True, classes=[15, 16, 57, 59],
                     boxes=True)

        # 算法部分, 计算狗和沙发的位置情况, 并且触发报警
        pred_classes = [classes[int(i.item())] for i in outs[0].boxes.cls]
        pred_bbox = [i.tolist() for i in outs[0].boxes.xywh]

        length = len(pred_classes)
        dog_boxes = []
        couch_boxes = []
        dog_flag, couch_bed_flag = 0, 0

        for i in range(length):
            if pred_classes[i] in ['dog', 'cat']:
                dog_boxes.append((round(pred_bbox[i][0]), round(pred_bbox[i][1]),
                                  round(pred_bbox[i][0] + pred_bbox[i][2]), round(pred_bbox[i][1] + pred_bbox[i][3])))
                dog_flag = 1
            if pred_classes[i] in ['couch', 'bed']:
                couch_boxes.append((round(pred_bbox[i][0]), round(pred_bbox[i][1]),
                                    round(pred_bbox[i][0] + pred_bbox[i][2]), round(pred_bbox[i][1] + pred_bbox[i][3])))
                couch_bed_flag = 1

        if alarm_played and (not dog_flag or not couch_bed_flag):
            dog_boxes = [(0, 0, 0, 0)]
            couch_boxes = [(0, 0, 0, 0)]
        else:
            for dog_box in dog_boxes:
                for couch_box in couch_boxes:
                    if dog_box[3] < (couch_box[3] - ((couch_box[3] - couch_box[1]) * 0.5)) and (
                            (couch_box[0] < dog_box[0] < couch_box[2]) or (
                            couch_box[0] < dog_box[2] < couch_box[2])) and dog_flag and couch_bed_flag:
                        not_detected = 0
                        if not alarm_played:
                            # 发送消息
                            # if datetime.now() - last_sent_time >= timedelta(seconds=15):
                            #     send_message(chat_id, "Dog has detected on couch!")
                            #     last_sent_time = datetime.now()
                            #     send_photo(chat_id, frame)
                            pygame.mixer.music.play(-1)  # play in a loop
                            alarm_played = True
                    else:
                        if alarm_played:
                            not_detected += 1
                            if not_detected > 10:
                                not_detected = 0
                                pygame.mixer.music.stop()
                                alarm_played = False

    couch_bed_flag = 0
    dog_flag = 0
    dog_boxes = [(0, 0, 0, 0)]
    couch_boxes = [(0, 0, 0, 0)]
    frame_counter += 1

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

cap.release()
cv2.destroyAllWindows()

运行代码,结果如下。视频

使用预训练模型获取小狗和沙发的目标框,并且计算两者的关系,以此来触发报警。

/ 03 /

车辆检测器

这是一个交通监控系统的项目。

使用OpenCV和YOLOv8实现如下功能,实时车辆检测、车辆跟踪、实时车速检测,以及检测车辆是否超速。

跟踪代码如下,赋予每个目标唯一ID,避免重复计算。

代码语言:javascript
复制
import math


class Tracker:
    def __init__(self):
        # 存储目标的中心位置
        self.center_points = {}
        # ID计数
        # 每当检测到一个新的目标id时, 计数将增加1
        self.id_count = 0

    def update(self, objects_rect):
        # 目标的方框和ID
        objects_bbs_ids = []

        # 获取新目标的中心点
        for rect in objects_rect:
            x, y, w, h = rect
            cx = (x + x + w) // 2
            cy = (y + y + h) // 2

            # 看看这个目标是否已经被检测到过
            same_object_detected = False
            for id, pt in self.center_points.items():
                dist = math.hypot(cx - pt[0], cy - pt[1])

                if dist < 35:
                    self.center_points[id] = (cx, cy)
                    # print(self.center_points)
                    objects_bbs_ids.append([x, y, w, h, id])
                    same_object_detected = True
                    break

            # 检测到新目标,分配ID给新目标
            if same_object_detected is False:
                self.center_points[self.id_count] = (cx, cy)
                objects_bbs_ids.append([x, y, w, h, self.id_count])
                self.id_count += 1

        # 按中心点清理字典, 删除不再使用的ID
        new_center_points = {}
        for obj_bb_id in objects_bbs_ids:
            _, _, _, _, object_id = obj_bb_id
            center = self.center_points[object_id]
            new_center_points[object_id] = center

        # 更新字典, 删除未使用的ID
        self.center_points = new_center_points.copy()
        return objects_bbs_ids

这是检测代码,速度计算那部分代码有点小粗糙。

最好是能有相机标定这一过程,这样速度才会准确。

代码语言:javascript
复制
import cv2
import time
import numpy as np
from tracker import *
import pandas as pd
from ultralytics import YOLO

# 加载模型
model = YOLO('yolov8s.pt')


def RGB(event, x, y, flags, param):
    if event == cv2.EVENT_MOUSEMOVE:
        colorsBGR = [x, y]
        print(colorsBGR)


cv2.namedWindow('RGB')
cv2.setMouseCallback('RGB', RGB)
cap = cv2.VideoCapture('Vid1.mp4')

my_file = open("coco.txt", "r")
data = my_file.read()
class_list = data.split("\n")
# print(class_list)

count = 0
speed = {}
area = [(225, 335), (803, 335), (962, 408), (57, 408)]
area_c = set()
# 跟踪算法
tracker = Tracker()
speed_limit = 62

while True:
    ret, frame = cap.read()
    if not ret:
        break
    count += 1
    if count % 3 != 0:
        continue
    frame = cv2.resize(frame, (1020, 500))

    results = model.predict(frame)
    # print(results)
    a = results[0].boxes.boxes
    px = pd.DataFrame(a).astype("float")
    # print(px)
    list = []

    # 使用YOLOv8的检测结果, 进行算法设计
    for index, row in px.iterrows():
        # print(row)

        x1 = int(row[0])
        y1 = int(row[1])
        x2 = int(row[2])
        y2 = int(row[3])
        d = int(row[5])
        c = class_list[d]
        if 'car' in c:
            list.append([x1, y1, x2, y2])
    bbox_id = tracker.update(list)
    for bbox in bbox_id:
        x3, y3, x4, y4, id = bbox
        cx = int(x3+x4)//2
        cy = int(y3+y4)//2
        results = cv2.pointPolygonTest(
            np.array(area, np.int32), ((cx, cy)), False)

        if results >= 0:
            cv2.circle(frame, (cx, cy), 4, (0, 0, 255), -1)
            cv2.putText(frame, str(id), (x3, y3), cv2.FONT_HERSHEY_COMPLEX,
                        0.8, (0, 255, 255), 2, cv2.LINE_AA)
            cv2.rectangle(frame, (x3, y3), (x4, y4), (0, 0, 255), 2)
            area_c.add(id)
            now = time.time()
            if id not in speed:
                speed[id] = now
            else:
                try:
                    prev_time = speed[id]
                    speed[id] = now
                    dist = 2
                    a_speed_ms = dist / (now - prev_time)
                    a_speed_kh = a_speed_ms * 3.6
                    cv2.putText(frame, str(int(a_speed_kh))+'Km/h', (x4, y4),
                                cv2.FONT_HERSHEY_COMPLEX, 0.8, (0, 255, 255), 2, cv2.LINE_AA)
                    speed[id] = now
                except ZeroDivisionError:
                    pass

                # 检查速度是否超过速度限制
                # if a_speed_kh >= speed_limit:
                #     # Display a warning message
                #     cv2.putText(frame, "Speed limit violated!", (440, 115),
                #                 cv2.FONT_HERSHEY_TRIPLEX, 0.8, (255, 0, 255), 2, cv2.LINE_AA)
                #     # Display the message for 3 seconds
                #     start_time = time.time()
                #     while time.time() - start_time < 3:
                #         cv2.imshow("RGB", frame)
                #         if cv2.waitKey(1) & 0xFF == 27:
                #             break

    cv2.polylines(frame, [np.array(area, np.int32)], True, (0, 255, 0), 2)
    cnt = len(area_c)
    cv2.putText(frame, ('Vehicle-Count:-')+str(cnt), (452, 50),
                cv2.FONT_HERSHEY_TRIPLEX, 1, (102, 0, 255), 2, cv2.LINE_AA)

    cv2.imshow("RGB", frame)
    if cv2.waitKey(1) & 0xFF == 27:
        break

# 刷新,释放资源
cap.release()
cv2.destroyAllWindows()

运行代码,结果如下。

发现效果还不错~

/ 04 /

总结

以上操作,就是三个使用YOLOv8实现的计算机视觉项目

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-27 20:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 法纳斯特 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档