如何实时检测并自动跟踪物体,而无需用户在要跟踪的物体周围绘制边界框?

3
我有以下代码,用户可以按下p键暂停视频,在要追踪的物体周围绘制边界框,然后按Enter(回车)来跟踪视频中的该物体:
import cv2
import sys

major_ver, minor_ver, subminor_ver = cv2.__version__.split('.')

if __name__ == '__main__' :

    # Set up tracker.
    tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT']
    tracker_type = tracker_types[1]

    if int(minor_ver) < 3:
        tracker = cv2.Tracker_create(tracker_type)
    else:
        if tracker_type == 'BOOSTING':
            tracker = cv2.TrackerBoosting_create()
        if tracker_type == 'MIL':
            tracker = cv2.TrackerMIL_create()
        if tracker_type == 'KCF':
            tracker = cv2.TrackerKCF_create()
        if tracker_type == 'TLD':
            tracker = cv2.TrackerTLD_create()
        if tracker_type == 'MEDIANFLOW':
            tracker = cv2.TrackerMedianFlow_create()
        if tracker_type == 'GOTURN':
            tracker = cv2.TrackerGOTURN_create()
        if tracker_type == 'MOSSE':
            tracker = cv2.TrackerMOSSE_create()
        if tracker_type == "CSRT":
            tracker = cv2.TrackerCSRT_create()

    # Read video
    video = cv2.VideoCapture(0) # 0 means webcam. Otherwise if you want to use a video file, replace 0 with "video_file.MOV")

    # Exit if video not opened.
    if not video.isOpened():
        print ("Could not open video")
        sys.exit()

    while True:

        # Read first frame.
        ok, frame = video.read()
        if not ok:
            print ('Cannot read video file')
            sys.exit()
        
        # Retrieve an image and Display it.
        if((0xFF & cv2.waitKey(10))==ord('p')): # Press key `p` to pause the video to start tracking
            break
        cv2.namedWindow("Image", cv2.WINDOW_NORMAL)
        cv2.imshow("Image", frame)
    cv2.destroyWindow("Image");

    # select the bounding box
    bbox = (287, 23, 86, 320)

    # Uncomment the line below to select a different bounding box
    bbox = cv2.selectROI(frame, False)

    # Initialize tracker with first frame and bounding box
    ok = tracker.init(frame, bbox)

    while True:
        # Read a new frame
        ok, frame = video.read()
        if not ok:
            break
        
        # Start timer
        timer = cv2.getTickCount()

        # Update tracker
        ok, bbox = tracker.update(frame)

        # Calculate Frames per second (FPS)
        fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);

        # Draw bounding box
        if ok:
            # Tracking success
            p1 = (int(bbox[0]), int(bbox[1]))
            p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
            cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
        else :
            # Tracking failure
            cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)

        # Display tracker type on frame
        cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);
    
        # Display FPS on frame
        cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);

        # Display result
        cv2.imshow("Tracking", frame)

        # Exit if ESC pressed
        k = cv2.waitKey(1) & 0xff
        if k == 27 : break

现在,我想让它能够在视频中自动检测我感兴趣的特定对象(在我的情况下是牙刷),而不是让用户暂停视频并画出物体的边界框,然后跟踪它。我在这篇文章中找到了一个使用ImageAI和Yolo来检测视频中物体的方法。
from imageai.Detection import VideoObjectDetection
import os
import cv2

execution_path = os.getcwd()

camera = cv2.VideoCapture(0) 

detector = VideoObjectDetection()
detector.setModelTypeAsYOLOv3()
detector.setModelPath(os.path.join(execution_path , "yolo.h5"))
detector.loadModel()

video_path = detector.detectObjectsFromVideo(camera_input=camera,
                                output_file_path=os.path.join(execution_path, "camera_detected_1")
                                , frames_per_second=29, log_progress=True)
print(video_path)

现在,Yolo确实可以检测牙刷,它是默认情况下可以检测到的80多种对象之一。但是,这篇文章有两个问题使其对我来说不是理想的解决方案:

  1. 该方法首先分析每个视频帧(每帧大约需要1-2秒的时间,因此从网络摄像头中分析2-3秒的视频流需要约1分钟),并将检测到的视频保存在单独的视频文件中。然而,我想要实时在网络摄像头视频中检测牙刷。是否有解决方案?

  2. 正在使用的Yolo v3模型可以检测所有80个对象,但我只想检测2或3个对象 - 牙刷、拿牙刷的人和可能需要的背景。因此,是否有一种方法可以通过选择仅这2或3个对象来检测来减少模型的权重?


你没有使用Darknet框架吗? - Yunus Temurlenk
我对此一无所知。在计算机视觉领域,我的经验不多,我只是想进入这个领域。因此,如果您认为darknet可以帮助解决这个问题,我将非常感激您能够写下如何解决的答案。 - Kristada673
2个回答

1
如果你想要一个快速简单的解决方案,可以使用其中一个更轻量级的yolo文件。你可以从这个网站获得权重和配置文件(它们成对出现,必须一起使用):https://pjreddie.com/darknet/yolo/(别担心,它看起来很奇怪但没事)。
使用一个较小的网络将会让你获得更高的帧率,但精度会变差。如果你愿意接受这种权衡,那么这是最容易实现的方法。
以下是一些用于检测牙刷的代码。第一个文件只是一个类文件,帮助使Yolo网络的使用更加无缝。第二个文件是“主”文件,它打开VideoCapture并向网络提供图像。

yolo.py

import cv2
import numpy as np

class Yolo:
    def __init__(self, cfg, weights, names, conf_thresh, nms_thresh, use_cuda = False):
        # save thresholds
        self.ct = conf_thresh;
        self.nmst = nms_thresh;

        # create net
        self.net = cv2.dnn.readNet(weights, cfg);
        print("Finished: " + str(weights));
        self.classes = [];
        file = open(names, 'r');
        for line in file:
            self.classes.append(line.strip());

        # use gpu + CUDA to speed up detections
        if use_cuda:
            self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA);
            self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA);

        # get output names
        layer_names = self.net.getLayerNames();
        self.output_layers = [layer_names[i[0]-1] for i in self.net.getUnconnectedOutLayers()];

    # runs detection on the image and draws on it
    def detect(self, img, target_id):
        # get detection stuff
        b, c, ids, idxs = self.get_detection_data(img, target_id);

        # draw result
        img = self.draw(img, b, c, ids, idxs);
        return img, len(idxs);

    # returns boxes, confidences, class_ids, and indexes (indices?)
    def get_detection_data(self, img, target_id):
        # get output
        layer_outputs = self.get_inf(img);

        # get dims
        height, width = img.shape[:2];

        # filter thresholds and target
        b, c, ids, idxs = self.thresh(layer_outputs, width, height, target_id);
        return b, c, ids, idxs;

    # runs the network on an image
    def get_inf(self, img):
        # construct a blob
        blob = cv2.dnn.blobFromImage(img, 1 / 255.0, (416,416), swapRB=True, crop=False);

        # get response
        self.net.setInput(blob);
        layer_outputs = self.net.forward(self.output_layers);
        return layer_outputs;

    # filters the layer output by conf, nms and id
    def thresh(self, layer_outputs, width, height, target_id):
        # some lists
        boxes = [];
        confidences = [];
        class_ids = [];

        # each layer outputs
        for output in layer_outputs:
            for detection in output:
                # get id and confidence
                scores = detection[5:];
                class_id = np.argmax(scores);
                confidence = scores[class_id];

                # filter out low confidence
                if confidence > self.ct and class_id == target_id:
                    # scale bounding box back to the image size
                    box = detection[0:4] * np.array([width, height, width, height]);
                    (cx, cy, w, h) = box.astype('int');

                    # grab the top-left corner of the box
                    tx = int(cx - (w / 2));
                    ty = int(cy - (h / 2));

                    # update lists
                    boxes.append([tx,ty,int(w),int(h)]);
                    confidences.append(float(confidence));
                    class_ids.append(class_id);

        # apply NMS
        idxs = cv2.dnn.NMSBoxes(boxes, confidences, self.ct, self.nmst);
        return boxes, confidences, class_ids, idxs;

    # draw detections on image
    def draw(self, img, boxes, confidences, class_ids, idxs):
        # check for zero
        if len(idxs) > 0:
            # loop over indices
            for i in idxs.flatten():
                # extract the bounding box coords
                (x,y) = (boxes[i][0], boxes[i][1]);
                (w,h) = (boxes[i][2], boxes[i][3]);

                # draw a box
                cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255), 2);

                # draw text
                text = "{}: {:.4}".format(self.classes[class_ids[i]], confidences[i]);
                cv2.putText(img, text, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 2);
        return img;

main.py

import cv2
import numpy as np

# this is the "yolo.py" file, I assume it's in the same folder as this program
from yolo import Yolo

# these are the filepaths of the yolo files
weights = "yolov3-tiny.weights";
config = "yolov3-tiny.cfg";
labels = "yolov3.txt";

# init yolo network
target_class_id = 79; # toothbrush
conf_thresh = 0.4; # less == more boxes (but more false positives)
nms_thresh = 0.4; # less == more boxes (but more overlap)
net = Yolo(config, weights, labels, conf_thresh, nms_thresh);

# open video capture
cap = cv2.VideoCapture(0);

# loop
done = False;
while not done:
    # get frame
    ret, frame = cap.read();
    if not ret:
        done = cv2.waitKey(1) == ord('q');
        continue;

    # do detection
    frame, _ = net.detect(frame, target_class_id);

    # show
    cv2.imshow("Marked", frame);
    done = cv2.waitKey(1) == ord('q');

如果您不想使用较轻的权重文件,有几个选项可供选择。
如果您有 Nvidia GPU,您可以使用 CUDA 来大幅提高 fps。即使是适度的 Nvidia GPU 也比仅用 CPU 运行快几倍。
绕过持续运行检测的成本的常见策略是仅在最初获取目标时使用它。您可以使用神经网络的检测来初始化对象跟踪器,类似于人们在物体周围画出边界框。对象跟踪器速度更快,而且每帧不需要不断地进行完整的检测。
如果您在单独的线程中运行 Yolo 和对象跟踪,则可以以相机的最大速度运行。您需要存储一系列帧,这样当 Yolo 线程完成一帧时,您可以检查旧帧,以查看是否已经跟踪到该对象,并且可以在相应的帧上启动对象跟踪器并将其快进以让其追上。该程序并不简单,您需要确保正确管理线程之间的数据。然而,这是一个练习多线程编程的好机会,这是编程中的一个重要步骤。

谢谢Ian。我已经下载了.weights.cfg文件,并将它们下载到与这两个.py文件保存在同一文件夹中的位置。但是当我运行main.py时,我遇到了以下错误:File "yolo.py", line 11, in __init__ self.net = cv2.dnn.readNet(weights, cfg); cv2.error: OpenCV(4.5.1) /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/pip-req-build-yvyj7qlp/opencv/modules/dnn/src/darknet/darknet_importer.cpp:207: error: (-212:Parsing error) Failed to parse NetParameter file: yolov3-tiny.cfg in function 'readNetFromDarknet' - Kristada673
它说找不到配置文件。请确保代码中的文件名与您下载的配置文件匹配。 - Ian Chu
搞定了。你说得对,我下载的.weights.cfg文件的名称与代码期望的不同。我还用YoloV4模型替换了YoloV3,并调整了conf_threshnms_thresh值,以使其在我的环境(照明条件、网络摄像头质量、要检测的对象等)中比YoloV3模型更好地工作。谢谢!现在,如果我想检测牙刷是否在人的嘴里,该怎么办?因为Yolo无法检测人的嘴巴,它只能将整个人检测出来,对吗? - Kristada673
是的,网站上的权重是在coco数据集上训练的,所以只有整个人。OpenCV可以实时进行面部标记。使用它来寻找嘴巴并检查牙刷是否与其重叠。 - Ian Chu

0

我想通过这篇文章来回答您的问题,我之前也用过它,并遇到了类似的问题。以下是我的建议:

  • 使用 darknet framework 运行 YOLOv3,可以提高性能。
  • 在您的代码片段中,看起来不允许您决定网络输入宽度和高度,因此我不知道您用什么来代替它们。减小网络宽度和高度将提高速度,但会相应地降低精度。
  • YOLOv3针对80个对象进行了训练,但您只需要其中的一些。我之前在项目中也只需要汽车。不幸的是,您不能操作已经训练好的权重文件,也不能很好地训练您自己的对象。
  • 我之前尝试过的另一种方法是将YOLOv3转移到另一个线程,而且我也没有将yolo应用于所有帧。我只应用了其中的一些,例如:每10帧中的1帧。这对我也有所帮助。
  • 或者您可以选择更好的CPU电脑 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接