年底奉献-QT编写视频监管平台(开源)
忙忙碌碌又是一年,算算自己毕业四年半,一直在现在这家公司做研发外加总经理助理,研发起初用的VB.NET,而后全面转为C#,最后又全面转为QT,都是由于项目需要,算下来自己搞QT编程也已经四年了,2010年开始接触QT并编写一些公司需要的辅助工具,其实搞程序的,我感觉绝大部分都是出于本身兴趣爱好,然后持之以恒的钻研,不断成长和进步。
项目需求:某区下面有几百所学校,每个学校都有若干台NVR或者DVR,每台NVR和DVR都挂接着N个IPC(摄像机)(包括网络摄像机和模拟摄像机),现在需要对所有学校的监控进行查看以及回放和轮询,能够对指定学校进行视频监控,对所有学校的视重点部位视频进行查看轮询,可自定义轮询时间等。
开发过程:本着尽量追求简洁的要求,最终编写了如上图的主界面。没有采用QT自带的界面,而是重写了界面,自定义无边框拖动,自由换肤,全部采用QSS控制,从官网http://qt-project.org/doc/qt-4.8/stylesheet-examples.html彻底学习了下QSS的规则,整理了一套通用的换肤方案。
PS:这是6年前写的项目,新版在这,不开源 https://www.cnblogs.com/feiyangqingyun/p/10913375.html
整个系统在开始架构的时候,本人都是写在草稿纸上的,包括布局,功能点,需要注意的处理等方面,现在要重新一一仔细写出来,还真不容易,这里就说个大概,然后将其中的部分功能处理用代码描述。
功能点罗列:
1:只限定一个实例处理。
视频监管平台是一个独占视频通道资源的系统,不能运行多个实例在同一台电脑上运行,所以在main函数中就限制了一个实例运行。
QSharedMemory mem("VM");
if (!mem.create()) {
myHelper::ShowMessageBoxError("程序已运行,软件将自动关闭!");
return ;
}
其中VM为自定义的名称,return 1表示退出程序返回1给操作系统。
如果重复运行会弹出如下提示:
2:F1键进入全屏模式,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(, , , );
ui->widget_main->layout()->setContentsMargins(, , , );
ui->widget_title->setVisible(false);
ui->treeMain->setVisible(false);
} void frmMain::screen_normal()
{
this->setGeometry(qApp->desktop()->availableGeometry());
this->layout()->setContentsMargins(, , , );
ui->widget_main->layout()->setContentsMargins(, , , );
ui->widget_title->setVisible(true);
ui->treeMain->setVisible(true);
}
3:支持QT4到QT5各个版本编译运行。
QT5与QT4的区别还是让很多搞QT开发的同学着实生气了一把,好端端的把一些方法去除掉了,而且有些头文件重新移到了其他地方,为了兼容QT4与QT5,在项目中就需要增加很多对版本的判断了。
例如头文件的包含:
#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif
例如设置UTF-8编码:
static void SetUTF8Code() {
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
QTextCodec::setCodecForLocale(codec);
QTextCodec::setCodecForCStrings(codec);
QTextCodec::setCodecForTr(codec);
#endif
}
4:基本常用的数据库处理,添加删除修改操作,表格显示。
本人一直喜欢采用拼接sql字符串来执行SQL语句。觉得这样运行效率很高,而且这种方法通用任何编程语言。
void frmIPC::on_btnAdd_clicked()
{
QString IPCID = ui->cboxIPCID->currentText();
QString IPCName = ui->txtIPCName->text();
QString NVRID = ui->cboxNVRID->currentText();
QString NVRName = ui->cboxNVRName->currentText();
QString IPCType = ui->cboxIPCType->currentText();
QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
QString IPCUserName = ui->txtIPCUserName->text();
QString IPCUserPwd = ui->txtIPCUserPwd->text();
QString IPCUse = ui->cboxIPCUse->currentText(); if (IPCName == "") {
myHelper::ShowMessageBoxError("名称不能为空,请重新填写!");
ui->txtIPCName->setFocus();
return;
} if (NVRName == "") {
myHelper::ShowMessageBoxError("NVR名称不能为空,请先添加好NVR!");
return;
} if (IPCRtspAddrMain == "") {
myHelper::ShowMessageBoxError("主码流地址不能为空,请重新填写!");
ui->txtIPCRtspAddrMain->setFocus();
return;
} if (IPCRtspAddrSub == "") {
myHelper::ShowMessageBoxError("子码流地址不能为空,请重新填写!");
ui->txtIPCRtspAddrSub->setFocus();
return;
} //检测编号是否唯一
if (IsExistIPCID(NVRID, IPCID)) {
myHelper::ShowMessageBoxError("编号已经存在,请重新选择!");
return;
} QSqlQuery query;
QString sql = "insert into [IPCInfo](";
sql += "[IPCID],[IPCName],[NVRID],[NVRName],";
sql += "[IPCType],[IPCRtspAddrMain],[IPCRtspAddrSub],";
sql += "[IPCUserName],[IPCUserPwd],[IPCUse])";
sql += "values('";
sql += IPCID + "','";
sql += IPCName + "','";
sql += NVRID + "','";
sql += NVRName + "','";
sql += IPCType + "','";
sql += IPCRtspAddrMain + "','";
sql += IPCRtspAddrSub + "','";
sql += IPCUserName + "','";
sql += IPCUserPwd + "','";
sql += IPCUse + "')";
query.exec(sql); LoadIPCInfo(); ui->cboxIPCID->setCurrentIndex(ui->cboxIPCID->currentIndex() + );
ui->txtIPCName->setText(QString("摄像机%1").arg(ui->cboxIPCID->currentText()));
} void frmIPC::on_btnDelete_clicked()
{
if (ui->tableMain->currentIndex().row() < ) {
myHelper::ShowMessageBoxError("请选择要删除的摄像机!");
return;
} QString tempIPCID = queryModule->record(
ui->tableMain->currentIndex().row())
.value().toString(); if (myHelper::ShowMessageBoxQuesion("确定要删除摄像机吗?") == ) {
QSqlQuery query;
QString sql = "delete from [IPCInfo] where [IPCID]='" + tempIPCID + "'";
query.exec(sql);
myHelper::Sleep(); //同步删除轮询表中的摄像机信息
sql = "delete from [PollInfo] where [IPCID]='" + tempIPCID + "'";
query.exec(sql);
myHelper::Sleep(); LoadIPCInfo();
}
} void frmIPC::on_btnUpdate_clicked()
{
if (ui->tableMain->currentIndex().row() < ) {
myHelper::ShowMessageBoxError("请选择要修改的摄像机!");
return;
} QString tempIPCID = queryModule->record(
ui->tableMain->currentIndex().row())
.value().toString(); QString IPCID = ui->cboxIPCID->currentText();
QString IPCName = ui->txtIPCName->text();
QString NVRID = ui->cboxNVRID->currentText();
QString NVRName = ui->cboxNVRName->currentText();
QString IPCType = ui->cboxIPCType->currentText();
QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
QString IPCUserName = ui->txtIPCUserName->text();
QString IPCUserPwd = ui->txtIPCUserPwd->text();
QString IPCUse = ui->cboxIPCUse->currentText(); if (IPCID != tempIPCID) {
//检测编号是否和已经存在的除自己之外的编号相同
if (IsExistIPCID(NVRID, IPCID)) {
myHelper::ShowMessageBoxError("编号已经存在,请重新选择!");
return;
}
} QSqlQuery query;
QString sql = "update [IPCInfo] set";
sql += " [IPCID]='" + IPCID;
sql += "',[IPCName]='" + IPCName;
sql += "',[NVRID]='" + NVRID;
sql += "',[NVRName]='" + NVRName;
sql += "',[IPCType]='" + IPCType;
sql += "',[IPCRtspAddrMain]='" + IPCRtspAddrMain;
sql += "',[IPCRtspAddrSub]='" + IPCRtspAddrSub;
sql += "',[IPCUserName]='" + IPCUserName;
sql += "',[IPCUserPwd]='" + IPCUserPwd;
sql += "',[IPCUse]='" + IPCUse;
sql += "' where [IPCID]='" + tempIPCID + "'";
query.exec(sql);
myHelper::Sleep(); //同步修改轮询表的信息
sql = "update [PollInfo] set";
sql += " [IPCID]='" + IPCID;
sql += "',[IPCName]='" + IPCName;
sql += "',[NVRID]='" + NVRID;
sql += "',[NVRName]='" + NVRName;
sql += "',[IPCRtspAddrMain]='" + IPCRtspAddrMain;
sql += "',[IPCRtspAddrSub]='" + IPCRtspAddrSub;
sql += "' where [IPCID]='" + tempIPCID + "'";
query.exec(sql);
myHelper::Sleep(); LoadIPCInfo();
}
5:QTreeView及QTableView数据加载和双击处理。
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().toString();
QString tempNVRName = queryNVR.value().toString();
QString tempNVRIP = queryNVR.value().toString(); QTreeWidgetItem *itemNVR = new QTreeWidgetItem
(ui->treeMain, QStringList(tempNVRName + "[" + tempNVRIP + "]"));
itemNVR->setIcon(, 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().toString(); //如果该摄像机已经存在轮询表,则跳过
if (IsExistIPCID(tempIPCID)) {
continue;
} QString tempIPCName = queryIPC.value().toString();
QString rtspAddr = queryIPC.value().toString(); QStringList temp = rtspAddr.split("/");
QString ip = temp[].split(":")[]; temp = QStringList(QString(tempIPCName + "[" + ip + "](" + tempIPCID + ")"));
QTreeWidgetItem *itemIPC = new QTreeWidgetItem(itemNVR, temp);
itemIPC->setIcon(, QIcon(":/image/ipc_normal.png"));
itemNVR->addChild(itemIPC);
}
}
ui->treeMain->expandAll();
}
6:16通道画面展示区域处理,自由切换1画面4画面9画面16画面。
void frmMain::show_video_4()
{
removelayout();
video_max = false;
int index = ; QAction *action = (QAction *)sender();
QString name = action->text();
if (name == "通道1-通道4") {
index = ;
myApp::VideoType = "1_4";
} else if (name == "通道5-通道8") {
index = ;
myApp::VideoType = "5_8";
} else if (name == "通道9-通道12") {
index = ;
myApp::VideoType = "9_12";
} else if (name == "通道13-通道16") {
index = ;
myApp::VideoType = "13_16";
} change_video_4(index);
myApp::WriteConfig();
} void frmMain::change_video_4(int index)
{
for (int i = (index + ); i < (index + ); i++) {
VideoLay[]->addWidget(VideoLab[i]);
VideoLab[i]->setVisible(true);
} for (int i = (index + ); i < (index + ); i++) {
VideoLay[]->addWidget(VideoLab[i]);
VideoLab[i]->setVisible(true);
}
}
7:精美开关按钮。
现在流行APP,各种APP上面都带有很精美的开关,参考了360安全卫士以及金山毒霸的开关按钮,用QT也实现了一个,原理很简单,就是贴图。
#include "switchbutton.h" /* 说明:自定义开关按钮控件实现文件
* 功能:用来控制配置文件的开关设置
* 作者:刘典武 QQ:517216493
* 时间:2013-12-19 检查:2014-1-10
*/
SwitchButton::SwitchButton(QWidget *parent): QPushButton(parent)
{
setCursor(QCursor(Qt::PointingHandCursor));
isCheck = false;
styleOn = "background-image: url(:/image/btncheckon.png); border: 0px;";
styleOff = "background-image: url(:/image/btncheckoff.png); border: 0px;"; setFocusPolicy(Qt::NoFocus);
setFixedSize(, ); //不允许变化大小
setStyleSheet(styleOff);//设置当前样式
connect(this, SIGNAL(clicked()), this, SLOT(ChangeOnOff()));
} void SwitchButton::ChangeOnOff()
{
if (isCheck) {
setStyleSheet(styleOff);
isCheck = false;
} else {
setStyleSheet(styleOn);
isCheck = true;
}
} //设置当前选中状态
void SwitchButton::SetCheck(bool isCheck)
{
if (this->isCheck != isCheck) {
this->isCheck = !isCheck;
ChangeOnOff();
}
}
8:重写过的消息框,错误框,询问框及输入框。
本人不喜欢系统的MessageBox,用QDialog重新布局自定义了一个。只需一句话调用即可。
在win7下运行截图如下:
在XP下运行截图如下:
在ubuntu上运行截图:
可执行文件下载:http://pan.baidu.com/s/1hqxhtbA
源码下载:http://pan.baidu.com/s/1mgFWeDU
编译运行后如果提示缺少数据库。将源码下的file文件夹下的配置文件config.txt及VM.db数据库文件复制到bin目录下即可。
说明:公开的源码去除了视频处理部分及样式部分,其余功能全部保留,并可完整编译运行。欢迎提出建议共同学习进步!
年底奉献-QT编写视频监管平台(开源)的更多相关文章
- Qt编写视频监控画面分割界面(开源)
其实qt应用在安防领域还是蛮多的,尤其是视频监控系统,但是网上几乎没有看到qt做的最基础的视频监控画面分割的demo,今天特意花几分钟提取出来,开源放出来.欢迎大家多多点赞!源码下载:点击打开链接 运 ...
- Qt编写的开源帖子集合(懒人专用)
回顾自己学习Qt以来九年了,在这九年多时间里面,从本论坛学习不到不少的东西,今天特意整了一下自己开源过的资源的帖子,整理一起方便大家直接跳转下载,不统计不知道,一统计吓一跳,不知不觉开源了这么多代码, ...
- Qt编写项目作品大全(自定义控件+输入法+大屏电子看板+视频监控+楼宇对讲+气体安全等)
一.自定义控件大全 (一).控件介绍 超过160个精美控件,涵盖了各种仪表盘.进度条.进度球.指南针.曲线图.标尺.温度计.导航条.导航栏,flatui.高亮按钮.滑动选择器.农历等.远超qwt集成的 ...
- Qt编写安防视频监控系统18-云台控制
一.前言 云台控制是视频监控系统中必备的一个功能,对球机进行上下左右的移动,还有焦距的控制,其实核心就是控制XYZ三个坐标轴,为了开发这个模块,特意研究了各种云台控制的方法和开源库比如soap,有些厂 ...
- Qt编写安防视频监控系统17-在线地图
一.前言 在线地图模块在一开始设计整个系统的时候就考虑进去了,主要功能就是在摄像机管理中,提供经纬度信息,然后加载百度地图在浏览器中显示,根据摄像机信息表中的每个摄像机的经纬度信息,自动生成设备点在地 ...
- Qt编写安防视频监控系统16-设备播放
一.前言 设备播放模块是后面增加的,核心就是通过组合rtsp视频流地址来播放实时视频和历史视频,目前市面上很多厂家比如排第一的海康都是支持直接rtsp通过NVR来播放某个通道视频流和回放某个通道的视频 ...
- Qt编写图片及视频TCP/UDP网络传输
一.前言 很多年前就做过类似的项目,无非就是将本地的图片上传到服务器,就这么简单,其实用http的post上传比较简单容易,无需自定义协议,直接设置好二进制数据即可,而采用TCP或者UDP通信的话,必 ...
- iSpy免费的开源视频监控平台
iSpy包括英文,Deutsch,Español,Française,Italiano和中文的翻译 iSpy是我们免费的开源视频监控平台.iSpy作为安装的Windows应用程序运行,具有完整的本地用 ...
- Qt编写安防视频监控系统(界面很漂亮)
一.前言 视频监控系统在整个安防领域,已经做到了烂大街的程序,全国起码几百家公司做过类似的系统,当然这一方面的需求量也是非常旺盛的,各种定制化的需求越来越多,尤其是这几年借着人脸识别的东风,发展更加迅 ...
随机推荐
- grunt使用小记之开篇:grunt概述
Grunt是什么? Grunt是一个自动化的项目构建工具.如果你需要重复的执行像压缩,编译,单元测试,代码检查以及打包发布的任务.那么你可以使用Grunt来处理这些任务,你所需要做的只是配置好Grun ...
- [安卓] 9、线程、VIEW、消息实现从TCP服务器获取数据动态加载显示
一.前言: 一般情况下从TCP服务器读取数据是放在一个线程里读的,但是刷新界面又不得不放在线程外面,所以需要用消息传递把线程里从TCP里获得的数据传送出来,然后根据数据对页面进行相应的刷新. 二.业务 ...
- [ACM_模拟][ACM_数学] LA 2995 Image Is Everything [由6个视图计算立方体最大体积]
Description Your new company is building a robot that can hold small lightweight objects. The robo ...
- SQL Server 2014 安装小记
一.写在前面 由于想体验下微软的Windows Azure在SQL Server数据库方面的使用,笔者花了点时间安装了一下SQL Server 2014,安装很简单,基本就是稍微做些配置即可,笔者在此 ...
- ComponentOne Xuni助力Xamarin开发者突破百万,快速开发Android、IOS Apps
在微软Build 2015上,随着VS 2015的预览版发布,Xamrine免费版已经作为VS 2015跨平台移动解决方案的核心.与此同时,Xamarin官方也宣布其用户量达到百万之多.2011年7月 ...
- 关于SQL SERVER的N前缀的理解
加N前缀指定后面的字符串为UNICODE常量, SQL Server 的 标准中的国家字符数据类型.SQL 使用前缀字符 N 标识这些数据类型及其值. , ),使用 个 个 Unicode字符时,使用 ...
- paip.提升效率--调试--日志系统日志参数含义---python
paip.提升效率--调试--日志系统日志参数含义---python #同时向控制台和文件输出日志 #日志参数含义 import logging log_format = '%(filename)s ...
- fir.im Weekly - 我回来了
Hey, 大家好,距离 fir.im 新版上线已匆忙过去一周多的时间,新版的fir正在慢慢稳定优化中,感谢大家的反馈与支持!后续我们将上线 FAQ 帮助中心,如还有疑问请邮件至 help@fir.im ...
- MVC5为WebAPI添加命名空间的支持
前言 默认情况下,微软提供的MVC框架模板中,WebAPI路由是不支持Namespace参数的.这导致一些比较大型的项目,无法把WebApi分离到单独的类库中. 本文将提供解决该问题的方案. 微软官方 ...
- 3D touch 环境配置
有人私信问我怎么配置环境,我就写个详细的,一步一步慢慢看,哈哈哈~ 打开下面 github, 记得FQ. https://github.com/DeskConnect/SBShortcutMenuSi ...