• 162阅读
  • 1回复

RK3588性能太逆天/2路8K30fps毫无压力/配置实在太凶残/视频分析AI盒子 [复制链接]

上一主题 下一主题
离线liudianwu
 



## 一、前言说明
最近搞嵌入式板子开发搞上瘾了,一连远程了十几个开发板上测试视频相关项目,比如视频综合应用和视频监控系统以及推流,试下来效果都不错,有RK3568/RK3576/RK3588等,厂家至少四五种,都大差不差,毕竟核心都是瑞星微的,可能涉及到一些版本问题,就是有些板子是老的mpp硬解,有些是新的,这样就需要用对应的硬解的ffmpeg库去做解码,不通用,因为ffmpeg编译的时候就链接了对应的库,版本不一样的话会导致编译通不过。在这几款板子中,3588是性能最强劲的,据说还有个3588s的要横空出世,又是一片血雨腥风。

现在好多都是用来做智能AI盒子,尤其是用来做行为分析,主要用yolo啥的,刚好我的视频监控系统集成了yolo运算后,对应的结果rect区域显示到对应视频上,多线程运行,也提供了从视频通道取一张图片数据传入yolo分析,搭配起来使用非常完美,**后期可能会定义一套私有协议,json格式数据,用于tcp/udp/http/mqtt等方式接收数据,用于插入日志记录、显示图文警情、显示人工智能结果(含物体区域比如人脸框和文字描述)等。这样第三方(比如只懂python的用户)只需要根据私有协议来就行。这样就不需要如何熟悉本软件源码,只需要会简单的使用即可。**

- 本软件目前本身没有AI人工智能算法加持,需要自行加上AI算法。
- 本软件内置的各种事件订阅是基于onvif协议,只要设备支持onvif协议,都可以正常接入。比如海康大华的摄像头和NVR等设备都支持前端做AI算法识别,将识别的结果通过onvif发给软件。
- 本软件内置了各种结果展示,包括报警文字记录,报警图像展示,报警视频录像、报警视频回放等,这些都是提供的通用的API函数接口调用使用,也就是直接Qt程序中调用全局静态API函数去处理。
- 本软件目前提供了相关类是DeviceThreadUI和DeviceVideoSmart。
- DeviceThreadUI专门演示如何在线程中插入警情记录和图文警情等信息、演示每隔一段时间插入消息、演示设备GPS移动、演示报警闪烁、演示指定通道编号设置视频和通道交换等功能。
- DeviceVideoSmart专门演示如何拿到一路视频数据,然后视频数据自己去做人工智能算法识别(比如用yolo),识别后的结果可以是一堆rect矩形区域,然后这些区域可以发回视频控件显示。这些计算后的区域信息也可以保存到视频文件或者重新推流出去(如果是推流需要额外购买推流组件)。
- **后期可能会定义一套私有协议,json格式数据,用于tcp/udp/http/mqtt等方式接收数据,用于插入日志记录、显示图文警情、显示人工智能结果(含物体区域比如人脸框和文字描述)等。这样第三方(比如只懂python的用户)只需要根据私有协议来就行。这样就不需要如何熟悉本软件源码,只需要会简单的使用即可。**
- 建议做二次开发的时候,单独起一个目录,新建个pri,后面所有的自己的处理都在这个目录中,隔离开来,方便后期拓展,这样如果监控系统源码有更新,也不需要大动干戈,只需要替换原有的监控系统源码即可。
- 如果想录像存储或者推流带上人工智能运算后的各种信息比如一堆rect或者文字水印,需要在系统设置中视频参数部分,编码下拉框选择自动,默认是不处理,不处理意味着保存的源头裸流264/265数据,自动的话就是编码保存,各种图形和文字水印都需要重新编码才能写入到文件。
- 如果开启了编码保存,则需要占用CPU资源,分辨率越大,占用越多,265比264占用更多。所以建议如果需要编码保存,运算那边尽量用子码流,也就是低分辨率的视频。

## 二、效果图




## 三、相关地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 文件地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_system。

## 四、功能特点
1. 主界面采用停靠窗体模式,各种组件以小模块的形式加入,可自定义任意模块加入。
2. 停靠模块可拖动任意位置嵌入和悬浮,支持最大化全屏,支持多屏幕。
3. 双重布局文件存储机制,正常模式、全屏模式都对应不同的布局方案,自动切换和保存,比如全屏模式可以突出几个模块透明显示在指定位置,更具科幻感现代化。
4. 原创onvif协议机制,采用底层协议解析(udp广播搜索+http请求执行命令)更轻量易懂易学习拓展,不依赖任何第三方组件比如gsoap。
5. 原创数据导入、导出、打印机制,跨平台不依赖任何组件,瞬间导出数据。
6. 内置多个原创组件,宇宙超值超级牛逼,包括数据导入导出组件(导出到xls、pdf、打印)、数据库组件(数据库管理线程、自动清理数据线程、万能分页、数据请求等)、地图组件、视频监控组件、文件多线程收发组件、onvif通信组件、通用浏览器内核组件等。
7. 自定义信息框、错误框、询问框、右下角提示框(包含多种格式)等。
8. 精美换肤,高达20套皮肤样式随意更换,所有样式全部统一,包括菜单等。
9. 选中通道对应设备树节点高亮,选中通道节点对应视频控件高亮,方便查看当前通道信息。
10. 视频控件悬浮条可以自行增加多个按钮,监控界面底部小工具栏也可自行增加按钮。
11. 双击摄像机节点自动播放视频,双击节点自动依次添加视频,会自动跳到下一个,双击父节点自动添加该节点下的所有视频。可选主码流、子码流。
12. 录像机管理、摄像机管理,可添加删除修改导入导出打印信息,立即应用新的设备信息生成树状列表,不需重启。
13. 摄像机搜索支持一键搜索和批量添加,支持onvif的NVR一键添加子设备,可以手动设置开始地址和数量一键生成摄像机信息。
14. 可选多种内核自由切换,ffmpeg、vlc、mpv等,均可在pro中设置。推荐用ffmpeg,跨平台最多,默认提供好了linux和mac平台上编译好的库。
15. 支持windows、linux、macos等系统硬解码,还支持嵌入式linux RKMPP硬解码,可设置硬解码类型(dxva2、d3d11va、vaapi、vdpau等)。
16. 各种模块可以勾选是否激活,方便根据实际需求搭配各种组合,比如隐藏电子地图模块,隐藏远程回放模块只保留本地回放等。
17. 尽最大化可能,将常用的功能封装接口,全局静态函数调用,极其容易使用,提供各种使用示例,方便用户二开。
18. 默认采用opengl绘制视频,超低的CPU资源占用,支持yuyv和nv12两种格式绘制,性能爆表。
19. 标签和图形信息支持三种绘制方式,绘制到遮罩层、绘制到图片、源头绘制(对应信息可以存储到文件)。
20. 包括但不限于视频监控内核组件的所有功能,可参阅说明书中功能介绍 [视频监控内核](###8.1 视频监控内核)。
21. 高度可定制化,用户可以很方便的在此基础上衍生自己的功能,比如增加自定义模块,增加运行模式、机器人监控、无人机监控、挖掘机监控、广播监控等。
22. 支持xp、win7、win10、win11、linux、mac、各种国产系统(UOS、中标麒麟、银河麒麟等)、嵌入式linux等系统。
23. 注释完整,项目结构清晰,超级详细完整的使用开发手册,精确到每个代码文件的功能说明,不断持续迭代版本。

## 五、相关代码
```cpp
//DeviceThreadUI类源码
#include "devicethreadui.h"
#include "devicehelper.h"
#include "dbquery.h"

DeviceThreadUI::DeviceThreadUI(QObject *parent) : QThread(parent)
{
    stopped = false;
}

void DeviceThreadUI::run()
{
    //先初始化随机数种子 否则生成的随机数很可能一致
    srand((quint64)currentThreadId());

    while (!stopped) {
        //演示每隔一段时间插入消息
        QString msg = "测试消息";
        QString result = "已处理";
        QString file = QString(":/image/liuyifei%1.jpg").arg(rand() % 3);
        //异步调用添加表格消息和图文警情
        QMetaObject::invokeMethod(this, "addMsg", Q_ARG(QString, msg));
        QMetaObject::invokeMethod(this, "addMsgList", Q_ARG(QString, msg), Q_ARG(QString, result), Q_ARG(QImage, QImage(file)));

        //演示设备GPS移动
        AppEvent::Instance()->slot_moveDevice(0, "121.415086", "31.183769");

        //演示报警闪烁
        if (1) {
            static bool isAlarm = true;
            isAlarm = !isAlarm;
            if (!isAlarm) {
                AppEvent::Instance()->slot_startAlarm();
                //演示插入一条带视频文件地址的报警记录用于双击播放
                DbQuery::addUserLog("0", "设备日志", "测试报警", "https://hls01open.ys7.com/openlive/6e0b2be040a943489ef0b9bb344b96b8.hd.m3u8");
            } else {
                AppEvent::Instance()->slot_stopAlarm();
                DbQuery::addUserLog("停止报警");
            }
        }

        //演示指定通道编号设置视频和通道交换
        if (1) {
            static int index = 0;
            if (index == 0) {
                AppEvent::Instance()->slot_loadVideo(0, "http://vfx.mtime.cn/Video/2023/03/09/mp4/230309152143524121.mp4");
            } else if (index == 1) {
                AppEvent::Instance()->slot_loadVideo(0, "http://vfx.mtime.cn/Video/2023/03/07/mp4/230307085324679124.mp4");
            } else if (index == 2) {
                AppEvent::Instance()->slot_loadVideo(1, "http://vfx.mtime.cn/Video/2022/07/18/mp4/220718132929585151.mp4");
            } else if (index == 3) {
                AppEvent::Instance()->slot_loadVideo(1, "http://vfx.mtime.cn/Video/2022/12/17/mp4/221217153424902164.mp4");
            } else if (index == 4) {
                AppEvent::Instance()->slot_changeVideo(2, 3);
            } else if (index == 5) {
                AppEvent::Instance()->slot_changeVideo(3, 2);
            }

            index++;
            if (index == 6) {
                index = 0;
            }
        }

        //延时一段时间
        msleep(5 * 1000);
    }

    stopped = false;
}

void DeviceThreadUI::addMsg(const QString &msg, quint8 type)
{
    DeviceHelper::addMsg(msg, type);
}

void DeviceThreadUI::addMsgList(const QString &msg, const QString &result, const QImage &image)
{
    DeviceHelper::addMsgList(msg, result, image);
}
```

//DeviceVideoSmart类源码:

#include "devicevideosmart.h"
#include "qthelper.h"
#include "videowidgetx.h"

DeviceVideoSmart::DeviceVideoSmart(QObject *parent) : QThread(parent)
{
    stopped = false;
    isPause = false;
    videoWidget = 0;

    //需要注册下这个类型
    qRegisterMetaType<QList<QRect> >("QList<QRect>");
}

void DeviceVideoSmart::run()
{
    //随机延时启动线程
    msleep(rand() % 500 + 1000);
    while (!stopped) {
        if (isPause || !AppConfig::VideoSmart || !videoWidget || !videoWidget->getIsRunning()) {
            msleep(1);
            continue;
        }

        //定义区域集合
        QList<QRect> rects;
        if (0) {
            //获取一张图片用于人工智能处理
            QImage image = videoWidget->getImage();
            qDebug() << TIMEMS << videoWidget->getVideoThread()->getFlag() << image.size();

            //人工智能处理算法放在这里进行运算
            //yolo->take(image);
        }

        //这里演示图形区域信息
        if (1) {
            //限定下区域
            int w = videoWidget->getVideoWidth();
            int h = videoWidget->getVideoHeight();
            int width = w - 100;
            int height = h - 100;
            int wx = w / 50;
            int hx = h / 50;

            //随机生成矩形
            for (int i = 0; i < 30; ++i) {
                int x = rand() % width;
                int y = rand() % height;
                int w = wx + rand() % 40;
                int h = hx + rand() % 30;
                rects << QRect(x, y, w, h);
            }

            //异步执行
            QMetaObject::invokeMethod(this, "receiveRects", Q_ARG(QList<QRect>, rects));
            msleep(40);
        }

        //这里演示文字标签信息
        if (0) {
            int currentCount = rand() % 15;
            int maxCount = rand() % 20 + 15;
            //qDebug() << TIMEMS << currentCount << currentThreadId();
            QMetaObject::invokeMethod(this, "receiveData", Q_ARG(QString, "dateTime"), Q_ARG(QVariant, 0));
            QMetaObject::invokeMethod(this, "receiveData", Q_ARG(QString, "currentCount"), Q_ARG(QVariant, currentCount));
            QMetaObject::invokeMethod(this, "receiveData", Q_ARG(QString, "maxCount"), Q_ARG(QVariant, maxCount));
            msleep(100);
        }

        //必须要有这个延时休息一下
        msleep(1);
    }

    stopped = false;
    isPause = false;
}

void DeviceVideoSmart::receiveKbps(qreal kbps, int frameRate)
{
    OsdInfo osd;
    osd.name = "kbps";
    osd.color = "#FF0000";
    osd.fontSize = fontSize;
    osd.position = OsdPosition_RightTop;
    osd.text = QString("%1 kbps\n%2 fps").arg((int)kbps).arg(frameRate);
    videoWidget->setOsd(osd);
}

void DeviceVideoSmart::receivePlayStart(int time)
{
    //字体大小按照视频宽度比例来
    fontSize = videoWidget->getVideoWidth() / 30;
}

void DeviceVideoSmart::receiveRects(const QList<QRect> &rects)
{
    if (!videoWidget || !videoWidget->getIsRunning()) {
        return;
    }

    int borderWidth = videoWidget->getVideoWidth() / 500;
    borderWidth = borderWidth < 1 ? 1 : borderWidth;

    //取随机颜色
    static QStringList colors = QColor::colorNames();
    int count = rects.count();
    for (int i = 0; i < count; ++i) {
        GraphInfo graph;
        graph.name = QString("graph%1").arg(i);
        graph.borderColor = colors.at(rand() % colors.count());
        graph.borderWidth = borderWidth;
        graph.rect = rects.at(i);
        videoWidget->setGraph(graph);
    }
}

void DeviceVideoSmart::receiveData(const QString &type, const QVariant &data)
{
    if (!videoWidget || !videoWidget->getIsRunning()) {
        return;
    }

    OsdInfo osd;
    osd.name = type;
    if (type == "dateTime") {
        osd.fontSize = fontSize;
        osd.color = "#FFFFFF";
        osd.format = OsdFormat_DateTime;
        osd.position = OsdPosition_LeftTop;
        videoWidget->setOsd(osd);
    } else if (type == "currentCount") {
        osd.fontSize = fontSize;
        osd.text = QString("当前人数: %1").arg(data.toInt());
        osd.color = "#D64D54";
        osd.format = OsdFormat_Text;
        osd.position = OsdPosition_LeftBottom;
        videoWidget->setOsd(osd);
    } else if (type == "maxCount") {
        osd.fontSize = fontSize;
        osd.text = QString("限定人数: %1").arg(data.toInt());
        osd.color = "#FDCD72";
        osd.format = OsdFormat_Text;
        osd.position = OsdPosition_RightBottom;
        videoWidget->setOsd(osd);
    }
}

void DeviceVideoSmart::setVideoWidget(VideoWidget *videoWidget)
{
    this->videoWidget = videoWidget;
    //关联实时码率信号将实时码率作为文字水印贴到视频上
    connect(videoWidget, SIGNAL(sig_receiveKbps(qreal, int)), this, SLOT(receiveKbps(qreal, int)));
    connect(videoWidget, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));
}

void DeviceVideoSmart::stop()
{
    stopped = true;
}

void DeviceVideoSmart::pause()
{
    isPause = true;
}
```
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线awfymwvf

只看该作者 1楼 发表于: 08-11
强力支持一下
爱好
快速回复
限100 字节
 
上一个 下一个