首页 > 代码库 > QT开发之旅五视频监管平台

QT开发之旅五视频监管平台

忙忙碌碌又是一年,算算自己毕业四年半,一直在现在这家公司做研发外加总经理助理,研发起初用的VB.NET,而后全面转为C#,最后又全面转为QT,都是由于项目需要,算下来自己搞QT编程也已经四年了,2010年开始接触QT并编写一些公司需要的辅助工具,其实在公司绝大部分时间在干杂七杂八的事情,例如采购、人事、招投标、现场技术支持等,一年下来大概只有不到三分之一的时间在钻研程序,其中这三分之一的时间晚上占了一大半,其实搞程序的,我感觉绝大部分都是出于本身兴趣爱好,然后持之以恒的钻研,不断成长和进步。

用QT陆续写过几个商业项目以及公司内部硬件通信需要的一些辅助设置工具,其中以报警视频联动管理平台为主,从2010年的C#版本开始编写,陆续完善积累到2014年的稳定版,花了相当多的精力去完善,不断查看同行的相关软件功能,吸取优秀的功能,摒弃无用的功能,用易用简洁全完善每一个细节。

今天要分享的是给某区安全管理中心编写的视频监管平台,稍后会放出QT4-QT5各版本可完整编译运行的源码。

按照习惯,首先上一张主界面截图:

XP下运行截图: 技术分享

Win7下运行截图:

技术分享

Ubuntu下运行截图:

技术分享整个系统在开始架构的时候,本人都是写在草稿纸上的,包括布局,功能点,需要注意的处理等方面,现在要重新一一仔细写出来,还真不容易,这里就说个大概,然后将其中的部分功能处理用代码描述。

项目需求:某区下面有几百所学校,每个学校都有若干台NVR或者DVR,每台NVR和DVR都挂接着N个IPC(摄像机)(包括网络摄像机和模拟摄像机),现在需要对所有学校的监控进行查看以及回放和轮询,能够对指定学校进行视频监控,对所有学校的视重点部位视频进行查看轮询,可自定义轮询时间等。

开发过程:本着尽量追求简洁的要求,最终编写了如上图的主界面。没有采用QT自带的界面,而是重写了界面,自定义无边框拖动,自由换肤,全部采用QSS控制,本人从官网http://qt-project.org/doc/qt-4.8/stylesheet-examples.html彻底学习了下QSS的规则,整理了一套通用的换肤方案。

 

功能点罗列:

1:只限定一个实例处理。

 技术分享

视频监管平台是一个独占视频通道资源的系统,不能运行多个实例在同一台电脑上运行,所以在main函数中就限制了一个实例运行。

技术分享View Code 

其中VM为自定义的名称,return 1表示退出程序返回1给操作系统。 

2F1键进入全屏模式,Esc键退出全屏模式。

几乎所有的视频监控系统,主界面都支持全屏显示及esc退出全屏,在QT中我是这样实现的,重写了主界面的keyPressEvent事件,拦截按键消息,判断对应按键,调用全屏及普通模式的方法。

技术分享
void frmMain::keyPressEvent(QKeyEvent *event)
{
    //空格键进入全屏,esc键退出全屏
    switch(event->key()) {
    case Qt::Key_F1:
        screen_full();
        break;
    case Qt::Key_Escape:
        screen_normal();
        break;
    default:
        QDialog::keyPressEvent(event);
        break;
    }
}

void frmMain::screen_full()
{    
    this->setGeometry(qApp->desktop()->geometry());
    this->layout()->setContentsMargins(0000);
    ui->widget_main->layout()->setContentsMargins(0000);
    ui->widget_title->setVisible(false);
    ui->treeMain->setVisible(false);
}

void frmMain::screen_normal()
{    
    this->setGeometry(qApp->desktop()->availableGeometry());
    this->layout()->setContentsMargins(1111);
    ui->widget_main->layout()->setContentsMargins(5555);
    ui->widget_title->setVisible(true);
    ui->treeMain->setVisible(true);
}
View Code

3:支持QT4QT5各个版本编译运行。

之前放出过N个工具及一个安防报警管理系统的源码,有兴趣的可以点击这里查看:http://www.qtcn.org/bbs/read-htm-tid-56372.html,很多兄弟反应不能在QT5上编译通过,甚是操蛋。本人一直喜欢用QT4.7,两个原因,一个是项目需要,程序主要在ARM上面跑,觉得部分公司提供的硬件开发板核心板都是自带的QT4.7版本的运行库,其他版本编译的程序无法在4.7运行库下运行。一个是QT4.7版本很经典,在windows上打包只需要附带几个DLL即可,打包压缩下一般才七八兆,很小,方便。到现在的QT5.4,我擦,光插件库就需要130MB,我勒个去,这还让用户的电脑怎么活啊!

本项目并木有牵扯到windows.h头文件,连修改系统时间的方法都改用了通用的系统命令,所以只要稍微更改部分地方即可,我代码中用的是:

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))

#include <QtWidgets>

#endif

QT5以上的版本需要引入头文件QtWidgets,设置UTF8编码在QT5中已经取消了。

技术分享View Code 

将所有的toAscii()方法全部替换成了toLatin1(),本来QT4就支持toLatin1()方法,作用几乎一样,索性就所有的替换成了toLatin1()方法。 

4:精心编写的通用的excelhelper类,独创的不依赖任何office组件的导出数据到表格的处理,通用任何excel版本。

方法:

void ToExcel(QString fileName, QString sheetName,

                 QString title, QString columnNames[],

                 int columnWidths[], int columnCount, QStringList content);

具体实现可以下载源码下来看。 

5:7套精美样式换肤,整体界面换肤。

这个花了本人N多精力去一个个调颜色,一个个控件写对应样式,所以在源码中并不包含,

在主界面的右上角做了个小箭头用来更换样式。 

技术分享
void frmMain::change_style()
{
    QAction *action = (QAction *)sender();
    QString style = action->text();
    if (style == "淡蓝色") {
        myApp::AppStyle = ":/image/blue.css";
    } else if (style == "蓝色") {
        myApp::AppStyle = ":/image/dev.css";
    } else if (style == "灰色") {
        myApp::AppStyle = ":/image/gray.css";
    } else if (style == "黑色") {
        myApp::AppStyle = ":/image/black.css";
    } else if (style == "灰黑色") {
        myApp::AppStyle = ":/image/brown.css";
    } else if (style == "白色") {
        myApp::AppStyle = ":/image/white.css";
    } else if (style == "银色") {
        myApp::AppStyle = ":/image/silvery.css";
    }

    myHelper::SetStyle(myApp::AppStyle);
    myApp::WriteConfig();
}
View Code

6:自带中文翻译文件。

鼠标右键菜单,默认QT是英文的,客户看了很恼火,需要自己将中文翻译文件加入资源文件编译到可执行文件中。

技术分享
//设置为中文字符
    static void SetChinese() {
        QTranslator *translator = new QTranslator(qApp);
        translator->load(":/image/qt_zh_CN.qm");
        qApp->installTranslator(translator);
}
View Code

7:基本常用的数据库处理,添加删除修改操作,表格显示。

本人一直喜欢采用拼接sql字符串来执行SQL语句。觉得这样运行效率很高,而且这种方法通用任何编程语言。

8QTreeViewQTableView数据加载和双击处理。

如下图:

技术分享

技术分享
void frmPollConfig::LoadNVRIPC()
{
    ui->treeMain->clear();

    QSqlQuery queryNVR;
    QString sqlNVR = "select [NVRID],[NVRName],[NVRIP] from [NVRInfo] where [NVRUse]=‘启用‘";
    queryNVR.exec(sqlNVR);

    while (queryNVR.next()) {
        QString tempNVRID = queryNVR.value(0).toString();
        QString tempNVRName = queryNVR.value(1).toString();
        QString tempNVRIP = queryNVR.value(2).toString();

        QTreeWidgetItem *itemNVR = new QTreeWidgetItem
        (ui->treeMain, QStringList(tempNVRName + "[" + tempNVRIP + "]"));
        itemNVR->setIcon(0, QIcon(":/image/nvr.png"));

        //查询没有添加在轮询表中的摄像机信息
        QSqlQuery queryIPC;
        QString sqlIPC = "select [IPCID],[IPCName],[IPCRtspAddrMain] from [IPCInfo]";
        sqlIPC += " where [NVRID]=‘" + tempNVRID;
        sqlIPC += "‘ and [IPCUse]=‘启用‘";
        sqlIPC += " order by [IPCID] asc";
        queryIPC.exec(sqlIPC);

        while (queryIPC.next()) {
            QString tempIPCID = queryIPC.value(0).toString();

            //如果该摄像机已经存在轮询表,则跳过
            if (IsExistIPCID(tempIPCID)) {
                continue;
            }

            QString tempIPCName = queryIPC.value(1).toString();
            QString rtspAddr = queryIPC.value(2).toString();

            QStringList temp = rtspAddr.split("/");
            QString ip = temp[2].split(":")[0];

            temp = QStringList(QString(tempIPCName + "[" + ip + "](" + tempIPCID + ")"));
            QTreeWidgetItem *itemIPC = new QTreeWidgetItem(itemNVR, temp);
            itemIPC->setIcon(0, QIcon(":/image/ipc_normal.png"));
            itemNVR->addChild(itemIPC);
        }
    }
    ui->treeMain->expandAll();
}
View Code

916通道画面展示区域处理,自由切换1画面4画面9画面16画面。

这个功能看似简单,确花费我一个星期的时间完善。

如下图:

技术分享

技术分享View Code 

10:鼠标右键菜单及二级菜单处理。

技术分享
void frmMain::InitVideo()
{
    tempLab = 0;
    video_max = false;

    VideoLab.append(ui->labVideo1);
    VideoLab.append(ui->labVideo2);
    VideoLab.append(ui->labVideo3);
    VideoLab.append(ui->labVideo4);
    VideoLab.append(ui->labVideo5);
    VideoLab.append(ui->labVideo6);
    VideoLab.append(ui->labVideo7);
    VideoLab.append(ui->labVideo8);
    VideoLab.append(ui->labVideo9);
    VideoLab.append(ui->labVideo10);
    VideoLab.append(ui->labVideo11);
    VideoLab.append(ui->labVideo12);
    VideoLab.append(ui->labVideo13);
    VideoLab.append(ui->labVideo14);
    VideoLab.append(ui->labVideo15);
    VideoLab.append(ui->labVideo16);

    VideoLay.append(ui->lay1);
    VideoLay.append(ui->lay2);
    VideoLay.append(ui->lay3);
    VideoLay.append(ui->lay4);

    for (int i = 0; i < 16; i++) {
        VideoLab[i]->installEventFilter(this);
        VideoLab[i]->setProperty("labVideo"true);
        VideoLab[i]->setText(QString("通道%1").arg(i + 1));
    }

    menu = new QMenu(this);
    menu->setStyleSheet("font: 10pt \"微软雅黑\";");
    menu->addAction("删除当前视频"this, SLOT(delete_video_one()));
    menu->addAction("删除所有视频"this, SLOT(delete_video_all()));
    menu->addSeparator();
    menu->addAction("截图当前视频"this, SLOT(snapshot_video_one()));
    menu->addAction("截图所有视频"this, SLOT(snapshot_video_all()));
    menu->addSeparator();

    QMenu *menu1 = menu->addMenu("切换到1画面");
    menu1->addAction("通道1"this, SLOT(show_video_1()));
    menu1->addAction("通道2"this, SLOT(show_video_1()));
    menu1->addAction("通道3"this, SLOT(show_video_1()));
    menu1->addAction("通道4"this, SLOT(show_video_1()));
    menu1->addAction("通道5"this, SLOT(show_video_1()));
    menu1->addAction("通道6"this, SLOT(show_video_1()));
    menu1->addAction("通道7"this, SLOT(show_video_1()));
    menu1->addAction("通道8"this, SLOT(show_video_1()));
    menu1->addAction("通道9"this, SLOT(show_video_1()));
    menu1->addAction("通道10"this, SLOT(show_video_1()));
    menu1->addAction("通道11"this, SLOT(show_video_1()));
    menu1->addAction("通道12"this, SLOT(show_video_1()));
    menu1->addAction("通道13"this, SLOT(show_video_1()));
    menu1->addAction("通道14"this, SLOT(show_video_1()));
    menu1->addAction("通道15"this, SLOT(show_video_1()));
    menu1->addAction("通道16"this, SLOT(show_video_1()));

    QMenu *menu4 = menu->addMenu("切换到4画面");
    menu4->addAction("通道1-通道4"this, SLOT(show_video_4()));
    menu4->addAction("通道5-通道8"this, SLOT(show_video_4()));
    menu4->addAction("通道9-通道12"this, SLOT(show_video_4()));
    menu4->addAction("通道13-通道16"this, SLOT(show_video_4()));

    QMenu *menu9 = menu->addMenu("切换到9画面");
    menu9->addAction("通道1-通道9"this, SLOT(show_video_9()));
    menu9->addAction("通道8-通道16"this, SLOT(show_video_9()));

    menu->addAction("切换到16画面"this, SLOT(show_video_16()));
}
View Code

11QTableView列宽自动计算按比例分配,按照分辨率比例显示字段。

技术分享
void frmPollConfig::InitForm()
{
    ui->treeMain->header()->setVisible(false);
    ui->treeMain->setEditTriggers(QAbstractItemView::NoEditTriggers);

    //最后一列自动填充
    ui->tableMain->horizontalHeader()->setStretchLastSection(true);
    //奇数偶数行不同背景色
    ui->tableMain->setAlternatingRowColors(true);
    //选中整行,每次只允许选中一行
    ui->tableMain->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->tableMain->setSelectionMode(QAbstractItemView::SingleSelection);

    queryModule = new QSqlQueryModel(this);
    LoadPollInfo();

    QString columnNames[6]; //列名数组
    int columnWidths[6];    //列宽数组

    
//初始化表格列名和列宽
    columnNames[0] = "编号";
    columnNames[1] = "名称";
    columnNames[2] = "NVR编号";
    columnNames[3] = "NVR名称";
    columnNames[4] = "主码流地址";
    columnNames[5] = "子码流地址";

    int width = myApp::DeskWidth - 246;
    columnWidths[0] = width * 0.06;
    columnWidths[1] = width * 0.11;
    columnWidths[2] = width * 0.08;
    columnWidths[3] = width * 0.11;
    columnWidths[4] = width * 0.23;
    columnWidths[5] = width * 0.23;

    //依次设置列标题列宽
    for (int i = 0; i < 6; i++) {
        queryModule->setHeaderData(i, Qt::Horizontal, columnNames[i]);
        ui->tableMain->setColumnWidth(i, columnWidths[i]);
    }
}
View Code

12:精美开关按钮。

如下图:

技术分享

之前在C#中写过这样的自定义控件,直接移植过来,无非就是两张图片切换,然后自定义一个属性记录当前是否选中。 

13:重写过的消息框,错误框,询问框及输入框。

本人不喜欢系统的MessageBox,用QDialog重新布局自定义了一个。只需一句话调用即可。

技术分享

技术分享 

技术分享 

技术分享
//显示输入框
    static QString ShowInputBox(QString info, bool &ok) {
        frmInputBox input;
        input.SetMessage(info);
        ok = input.exec();
        return input.GetValue();
    }

    //显示信息框,仅确定按钮
    static void ShowMessageBoxInfo(QString info) {
        frmMessageBox msg ;
        msg.SetMessage(info, 0);
        msg.exec();
    }

    //显示错误框,仅确定按钮
    static void ShowMessageBoxError(QString info) {
        frmMessageBox msg;
        msg.SetMessage(info, 2);
        msg.exec();
    }

    //显示询问框,确定和取消按钮
    static int ShowMessageBoxQuesion(QString info) {
        frmMessageBox msg;
        msg.SetMessage(info, 1);
        return msg.exec();
}
View Code

14:所有代码精简重构,并带有注释,初学者都可以看懂和学会。

15:fontawesome图形字体的使用,为界面增添色彩。 

技术分享
#include "iconhelper.h"

IconHelper *IconHelper::_instance = 0;
IconHelper::IconHelper(QObject *):
    QObject(qApp)
{
    int fontId = QFontDatabase::addApplicationFont(":/image/fontawesome-webfont.ttf");
    QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
    iconFont = QFont(fontName);
}

void IconHelper::SetIcon(QLabel *lab, QChar c, int size)
{
    iconFont.setPointSize(size);
    lab->setFont(iconFont);
    lab->setText(c);
}

void IconHelper::SetIcon(QPushButton *btn, QChar c, int size)
{
    iconFont.setPointSize(size);
    btn->setFont(iconFont);
    btn->setText(c);
}
View Code

技术分享

界面上的最小化及关闭按钮,直接使用的图形字体,而不是贴图。

 

可执行文件下载:http://pan.baidu.com/s/1hqxhtbA

源码下载:http://pan.baidu.com/s/1mgFWeDU

编译运行后如果提示缺少数据库。将源码下的file文件夹下的配置文件config.txt及VM.db数据库文件复制到bin目录下即可。

 

说明:公开的源码去除了视频处理部分及样式部分,其余功能全部保留,并可完整编译运行。如果需要完整版本,可以支付宝赞助小弟辛苦费100元购买,这套系统可以当一个小型的视频监控系统用,比较完美,很少BUG。视频处理部分摸索了很久,曾经愿意花3000元购买类似解决方案无果,找到一家愿意以8000元的价格出售,无奈价格太高,整个私活的钱都不够,所以还是自己通宵费劲心思研究了出来。生活压力巨大!所以还望觉得完整版要收费的兄弟们口下留情,在此跪谢!

QT开发之旅五视频监管平台