安卓目标检测,目标跟踪,人流量计数

先看效果, 推理时间不到30ms。

727ce82bd55e12f28882c1a6f41ce313.gif
大多数目标检测,目标追踪网络模型部署在终端,用的Python,CUDA环境,只能参考流程,案例Python环境下实现的人流量计数,代码跑跑,效果如下
根据这个案例,了解到要实现人流量计数过程

先目标检测得到目标的位置,再通过跟踪算法根据目标的位置和图像特征,得到目标ID,再计算碰撞线,判断这个目标ID是上还是下。所以重点就是目标检测,目标跟踪。

方案一,NCNN+YOLOV5,推理时间270ms。

yolov5
ncnn
ncnn-android-yolov5

ncnn是一个为手机端极致优化的高性能神经网络前向计算框架。之前做了一个挂机打怪助手,自制数据集LabelImg,训练得到pt,转onnx,转ncnn,推理时间是270ms,有兴趣的可以研究下。

方案二,寻找一个第三方部署的平台,只用替换训练模型就跑起来,简单上手。

Paddle-Lite-Demo
百度的,Paddle-Lite 提供了多个应用场景的 demo,并支持 Android、iOS 和 ArmLinux 三个平台。

PaddleDetection
基于飞桨PaddlePaddle的端到端目标检测套件,内置30+模型算法及300+预训练模型,覆盖目标检测、实例分割、跟踪、关键点检测等方向,其中包括服务器端和移动端高精度、轻量级产业级SOTA模型、冠军方案和学术前沿算法,并提供配置化的网络模块组件、十余种数据增强策略和损失函数等高阶优化支持和多种部署方案,在打通数据处理、模型开发、训练、压缩、部署全流程的基础上,提供丰富的案例及教程,加速算法产业落地应用。

基本了解后,可以开始了

1. 准备俯拍人头数据集

1668390432230.jpg

2. VOC数据集格式,还需要label_list.txt,train.txt,valid.txt,生成即可

import os
import random

dirpath = 'D://FY/AI/head_datas/annotations'
jpgPath = 'D://FY/AI/head_datas/images'

train_output_file = 'D://FY/AI/head_datas/train.txt'
val_output_file = 'D://FY/AI/head_datas/valid.txt'

ftrain= open(train_output_file,"w")
fval= open(val_output_file,"w")
for (path, dirnames, filenames) in os.walk(dirpath):
    for filename in filenames:
        if filename.endswith('.xml'):
            img_path = './images/'+ filename[:len(filename)-4]+".jpg"+" ./annotations/"+filename+"\n"
            number = random.random()
            if number>0.08:
                ftrain.write(img_path)
            else:
                fval.write(img_path)

3. 下载Paddle-Lite-DemoPaddleDetection,例如我用的是

Paddle-Lite-Demo/object_detection/android/app/cxx/picodet_detection_demo

PaddleDetection/configs/picodet/legacy_model/picodet_s_320_coco.yml

如果使用不是Paddle-Lite-Demo中的模型结构, 需要使用Netron查看模型结构,对比原模型,如果输入输出不同,还需要修改对应的预处理和后处理函数。

4. 接下来的模型训练,评估,测试,导出可以参考PaddleDetection README

5. 运行安卓Demo,替换对应的模型db和Label。

6. 添加目标跟踪算法,计数。

参考于Multi-object tracking,包含了DeepSort,ByteTrack两种C++代码的目标跟踪算法,方便移植。
DeepSort还需要依赖onnxruntime,可以自行尝试。

我使用的是ByteTrack,代码移植到项目中app\src\main\cpp下,检测结构Object会和原来的冲突,替换下就行,我这里提取出来到一个头文件了,都引用这个这个。

#ifndef YOLO_DETECTION_DEMO_RESULT_H
#define YOLO_DETECTION_DEMO_RESULT_H

#include <opencv2/core.hpp>            // NOLINT
#include <opencv2/highgui/highgui.hpp> // NOLINT
#include <opencv2/imgcodecs.hpp>       // NOLINT
#include <opencv2/imgproc.hpp>         // NOLINT

struct Object {
    std::string class_name;
    cv::Scalar fill_color;
    float prob;
    cv::Rect rec;
    int class_id;
};

在已有的基础上添加跟踪,计数流程

void Pipeline::Process(Obj *obj, cv::Mat &rgbaImage, std::string savedImagePath) {
    double preprocessTime = 0, predictTime = 0, postprocessTime = 0, byteTrackTime = 0, countTime = 0;
    // Feed the image, run inference and parse the results
    std::vector<Object> results;
    detector_->Predict(rgbaImage, &results, &preprocessTime, &predictTime,
                       &postprocessTime);
    //添加跟踪
    std::vector<STrack> output_stracks = detector_->bytetrack(&rgbaImage, results, &byteTrackTime);
    //人流量计数
    int upCount = 0, downCount = 0;
    detector_->counttrack(&rgbaImage, results, &output_stracks, &countTime, &upCount, &downCount);
    if (!savedImagePath.empty()) {
        cv::Mat bgrImage;
        cv::cvtColor(rgbaImage, bgrImage, cv::COLOR_RGBA2BGR);
        imwrite(savedImagePath, bgrImage);
    }
    //运行时间和进出人数返回
    obj->preprocessTime = preprocessTime;
    obj->predictTime = predictTime;
    obj->postprocessTime = postprocessTime;
    obj->byteTrackTime = byteTrackTime;
    obj->countTime = countTime;
    obj->modified = true;
    obj->enter = upCount;
    obj->out = downCount;
}

跟踪方法

std::vector<STrack> Detector::bytetrack(const cv::Mat *frame, const std::vector<Object> &results,
                                        double *byteTrackTime) {
    auto t = GetCurrentTime();
    //LOGD("Detector -------------results size: %d ", objects.size());
    std::vector<STrack> output_stracks = bytetracker.update(results);
    *byteTrackTime = GetElapsedTime(t);
    // LOGD("Detector -------------output_stracks size: %d ", output_stracks.size());
    for (unsigned long i = 0; i < output_stracks.size(); i++) {
        std::vector<float> tlwh = output_stracks[i].tlwh;
        float tlwh0 = tlwh[0];
        float tlwh1 = tlwh[1];
        float tlwh2 = tlwh[2];
        float tlwh3 = tlwh[3];
        bool vertical = tlwh2 / tlwh3 > 1.6;
        if (tlwh2 * tlwh3 > 20 && !vertical) {

        }
        cv::Scalar s = bytetracker.get_color(output_stracks[i].track_id);

        cv::putText(*frame, cv::format("%d", output_stracks[i].track_id),
                    cv::Point(tlwh0, tlwh1 - 5),
                    0, 5, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
        cv::rectangle(*frame, cv::Rect(tlwh0, tlwh1, tlwh2, tlwh3), s, 2);
    }
    return output_stracks;
}

计数方法

void Detector::counttrack(const cv::Mat *frame, const std::vector<Object> &results,
                          std::vector<STrack> *output_strack, double *countTime, int *upCount,
                          int *downCount) {
    auto t = GetCurrentTime();
    bool hasPerson = false;
    if (results.size() > 0)
        hasPerson = true;
    if (!hasPerson) {
        upTrack.clear();
        downTrack.clear();
    } else {
        //1080
        float width = frame->cols;
        //607
        float height = frame->rows;
        float lineY = height * 0.5;
        //画上面的这个线
        cv::line(*frame, cv::Point(0, lineY), cv::Point(width, lineY), (245, 245, 245), 1);
        for (unsigned long i = 0; i < output_strack->size(); i++) {
            STrack sTrack = output_strack->at(i);
            std::vector<float> tlwh = sTrack.tlwh;
            //用下面的坐标值
            float pY = tlwh[1] + tlwh[3];
            //LOGD("Detector -------------%f",pY);
            //在分界线的上面
            if (pY < lineY) {
                bool exist = false;
                for (std::vector<int>::iterator iter = downTrack.begin();
                     iter != downTrack.end(); iter++) {
                    if ((*iter) == sTrack.track_id) {
                        exist = true;
                        LOGD("Detector -------------存在up++");
                        (*upCount)++;
                        downTrack.erase(iter);
                        break;
                    }
                }
                //如果不存在
                if (!exist) {
                    //如果没有找到
                    if (std::find(upTrack.begin(), upTrack.end(), sTrack.track_id) ==
                        upTrack.end()) {
                        upTrack.push_back(sTrack.track_id);
                    }
                }
            }
                //在分界线的下面
            else if (pY > lineY) {
                bool exist = false;
                for (std::vector<int>::iterator iter = upTrack.begin();
                     iter != upTrack.end(); iter++) {
                    if ((*iter) == sTrack.track_id) {
                        exist = true;
                        LOGD("Detector -------------存在down++");
                        (*downCount)++;
                        upTrack.erase(iter);
                        break;
                    }
                }
                //如果不存在
                if (!exist) {
                    //如果没有找到
                    if (std::find(downTrack.begin(), downTrack.end(), sTrack.track_id) ==
                        downTrack.end()) {
                        downTrack.push_back(sTrack.track_id);
                    }
                }
            }
        }
    }
    *countTime = GetElapsedTime(t);
}

总结

凡事都试一试

还有其他的一些移动端x86/arm 部署的平台,可以自行研究。
TensorflowLite
腾讯TNN
小米Mace
阿里MNN

计算机视觉学习视频,极力推荐北京邮电大学鲁鹏讲的 计算机视觉与深度学习

猜你喜欢

转载自blog.csdn.net/weixin_43141131/article/details/127843357