Qt摄像头电子地图Demo:局域网视频+定位+分组一体化监控原型
简介这是一份基于Qt 5.832位开发的摄像头电子地图可视化Demo源码面向嵌入式视觉、安防监控系统及GIS集成方向的Qt中级开发者解决多路网络摄像头在百度地图上的动态定位、分组管理与实时视频联动播放等典型工程问题。资源包共469个文件涵盖341个头文件h、17个C源码cpp、5个UI界面设计ui、1个SQLite数据库db、18个静态库a及33个导入库lib并包含FFmpeg相关音视频解码支持文件如libavcodec.dll.a等整体压缩后仅19.3MB轻量易部署。已有281人学习下载适合快速掌握Qt与百度地图API集成、设备树管理、右键菜单交互逻辑及SQLite本地设备配置等实战技能。读者可直接编译运行修改数据库即可适配自有局域网摄像头完整保留创建/删除组、拖拽设备、地图跳转、标注点增删、经纬度显示与缩放控制等全部功能模块。1. QT开发的摄像头电子地图Demo采用百度地图一个能真正在局域网里跑通视频定位拖拽分组的轻量级监控界面原型你有没有试过在Qt里把摄像头流、地图定位、SQLite设备管理三者串成一条可交互的链路不是只画个地图加几个点而是右键就能建组、拖设备进组、点设备自动跳转地图并播放RTSP流——这个Demo就是干这个的。它不依赖云平台、不走WebGL黑匣子渲染用的是Qt 5.8 32位原生控件百度地图JavaScript API嵌入本地SQLite数据库所有逻辑都在C/QML层可控。特别适合某高校安防课程设计、某公司内网小型监控系统原型验证或者作为Qt音视频GIS交叉模块的实操切口。关键它不玄学编译通过即能跑改几行数据库路径和摄像头URL就能对接你自己的局域网IPC密钥填对地图缩放、标注、经纬度反馈全实时。如果你正卡在“Qt怎么把视频窗口叠在网页地图上”“设备分组状态怎么持久化又不影响地图交互”“点击设备后地图如何精准跳转并保持缩放级别”这三个坎上这个工程源码就是一份带血泪经验的参考答案。2. 编译与运行环境准备为什么必须是Qt 5.8 32位libav系列.a文件的真实用途这个工程不是“下载解压双击运行”型它的构建链路决定了你必须先理清底层依赖的咬合关系。核心约束来自视频解码层——项目正文反复列出的libavcodec.dll.a等五个.a文件不是笔误而是MinGW静态链接库的典型命名.dll.a是MinGW生成的导入库用于链接对应DLL。它们对应的是FFmpeg的C接口封装但注意这里用的不是Qt Multimedia的默认后端而是手动调用libav解码QPainter绘制YUV帧。这意味着Qt版本必须匹配MinGW 5.3Qt 5.8官方预编译版标配否则.a文件符号表不兼容必须用32位工具链因为提供的avcodec-57.dll等动态库只有32位版本项目摘要明确提示“视频播放的码流库是32位”不能简单替换为Qt 6.x或MSVC编译器——QWebEngineView与libav的线程模型、OpenGL上下文在64位MSVC下会冲突。2.1 开发环境清单与验证步骤提示不要跳过验证。很多“编译成功但运行白屏”的问题根源在环境校验没做全。首先确认你的Qt Creator安装路径下存在以下组件以Windows为例# 进入Qt安装目录检查MinGW工具链是否存在 ls C:\Qt\5.8\mingw53_32\bin\ | findstr g.exe # 应返回g.exe # 检查qmake是否指向正确版本 C:\Qt\5.8\mingw53_32\bin\qmake.exe -v # 输出必须含Using Qt version 5.8.0 in C:/Qt/5.8/mingw53_32/lib接着验证FFmpeg DLL是否在运行时可加载非编译时# 将项目根目录下的 libav*.dll 复制到 build 目录与可执行文件同级 copy libavcodec-57.dll build\ copy libavformat-57.dll build\ copy libavutil-55.dll build\ copy libavdevice-57.dll build\ copy libavfilter-6.dll build\注意.dll.a文件仅用于编译链接qmake.pro里LIBS -L$$PWD -lavcodec -lavformat...真正运行靠的是同名.dll。若漏复制DLL程序启动时会弹窗报“找不到libavcodec-57.dll”而非编译错误。2.2 qmake工程配置关键参数解析打开项目根目录下的cameramap.pro重点看以下三段配置# 1. 强制指定32位MinGW套件防止Qt Creator自动选错Kit QT core gui widgets webenginewidgets CONFIG c11 QMAKE_CXXFLAGS -stdgnu11 # 2. FFmpeg库链接路径与符号注意路径用$$PWD表示项目根目录 LIBS -L$$PWD -lavcodec -lavformat -lavutil -lavdevice -lavfilter INCLUDEPATH $$PWD/include/ffmpeg # 3. 百度地图JS API加载控制关键避免跨域拦截 contains(QT_VERSION, ^5\.8\.) { WEBENGINE_CONFIG disable-web-security }最后一行disable-web-security是硬性要求。因为百度地图API在本地file://协议下加载时默认禁止AJAX请求如获取地理编码、逆地理编码而本工程的“地点定位”功能需调用BMap.Geocoder()。若不加此配置控制台会报XMLHttpRequest cannot load ... No Access-Control-Allow-Origin header定位按钮点击无响应。2.3 SQLite数据库初始化与设备录入规范工程使用camera.db存储设备元数据结构由init_db.sql初始化-- camera.db 表结构精简核心字段 CREATE TABLE IF NOT EXISTS groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT 未分组 ); CREATE TABLE IF NOT EXISTS cameras ( id INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER NOT NULL, name TEXT NOT NULL, rtsp_url TEXT NOT NULL, lat REAL NOT NULL, lng REAL NOT NULL, status INTEGER DEFAULT 0 -- 0:停用, 1:启用 );关键约束rtsp_url必须是完整格式例如rtsp://admin:password192.168.1.100:554/stream1。若密码含特殊字符如、/必须URL编码%40代替。否则libav解码器初始化失败日志显示Invalid data found when processing input。插入测试设备的SQL示例直接在SQLite命令行或DB Browser for SQLite中执行-- 先插入一个组 INSERT INTO groups (name) VALUES (东区监控); -- 再插入设备注意group_id必须存在 INSERT INTO cameras (group_id, name, rtsp_url, lat, lng) VALUES (1, 东门岗亭, rtsp://admin:123456192.168.1.101:554/stream1, 39.915, 116.404);注意经纬度必须用小数形式非度分秒且范围需符合百度地图坐标系BD-09。若填入WGS-84坐标如GPS原始值地图会偏移500米以上。工程未内置坐标转换需自行用百度API或离线算法转换。3. 核心功能实现原理从右键菜单到地图跳转的信号链路拆解这个Demo的交互流畅感本质是Qt信号槽与JavaScript桥接的精密配合。它没有用QWebChannel太重而是用最朴素的QWebEngineView::page()-runJavaScript()注入执行再通过window.qtWidget全局对象回调C。我们以“点击设备→地图跳转→播放视频”这一主链路为例逐层剥开。3.1 右键菜单事件捕获与设备ID传递界面中每个设备图标实际是QGraphicsPixmapItem其contextMenuEvent()被重载// cameraview.cpp void CameraView::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { QPointF scenePos mapToScene(event-pos()); // 遍历所有设备图元判断是否点击在图标内 foreach (auto item, m_cameraItems) { if (item-contains(item-mapFromScene(scenePos))) { m_rightClickedCameraId item-data(CAMERA_ID).toInt(); // 关键存ID showCameraContextMenu(event-screenPos()); return; } } }逻辑说明m_cameraItems是设备图元列表每个item通过setData(CAMERA_ID, id)绑定数据库ID。这样右键时无需查询数据库直接拿到ID避免UI线程阻塞。右键菜单“播放设备”触发后调用void CameraView::onPlayCamera() { // 1. 查询数据库获取该设备的经纬度和RTSP地址 QSqlQuery query(m_db); query.prepare(SELECT lat, lng, rtsp_url FROM cameras WHERE id ?); query.addBindValue(m_rightClickedCameraId); query.exec(); if (query.next()) { double lat query.value(0).toDouble(); double lng query.value(1).toDouble(); QString rtspUrl query.value(2).toString(); // 2. 通知地图JS跳转并传RTSP地址给视频播放器 QString js QString(R( window.map.centerAndZoom(new BMap.Point(%1, %2), 15); window.qtWidget.startVideo(%3); )).arg(lng).arg(lat).arg(rtspUrl.replace(, \\)); m_webView-page()-runJavaScript(js); } }参数说明BMap.Point(lng, lat)注意百度地图参数顺序是经度, 纬度与常见GIS习惯相反rtspUrl.replace(, \\)防止单引号破坏JS字符串这是血泪经验——曾因URL含导致JS语法错误控制台静默失败。3.2 百度地图JS层与Qt的双向通信机制HTML模板index.html中定义了window.qtWidget对象!-- index.html -- script window.qtWidget { startVideo: function(url) { // 1. 触发Qt的自定义信号 if (typeof qtWidget ! undefined) { qtWidget.videoStart(url); // 此处调用Qt注册的slot } }, updateMousePosition: function(lat, lng) { // 2. 将鼠标经纬度回传给Qt if (typeof qtWidget ! undefined) { qtWidget.mouseMoved(lat, lng); } } }; /scriptC侧通过QWebChannel暴露对象注意虽未用QWebChannel传输大数据但用它暴露函数是安全的// mainwindow.cpp QWebChannel *channel new QWebChannel(this); channel-registerObject(QStringLiteral(qtWidget), this); m_webView-page()-setWebChannel(channel);然后实现两个Q_INVOKABLE槽函数// mainwindow.h Q_INVOKABLE void videoStart(const QString url); Q_INVOKABLE void mouseMoved(double lat, double lng); // mainwindow.cpp void MainWindow::videoStart(const QString url) { // 启动libav解码线程将YUV帧推送至QPainter绘制区域 m_videoPlayer-start(url); // m_videoPlayer是自定义AVPlayer类 } void MainWindow::mouseMoved(double lat, double lng) { // 更新状态栏显示 ui-statusBar-showMessage(QString(鼠标位置%.6f, %.6f).arg(lat).arg(lng)); }关键设计地图JS不直接操作视频只负责定位视频播放完全由Qt C层控制。这样避免了WebGL与QPainter渲染上下文冲突也便于调试——若视频卡顿可单独测AVPlayer类与地图解耦。3.3 设备拖拽分组的物理引擎模拟“支持设备拖动到其他组”不是简单改变数据库group_id而是实现了带吸附效果的拖拽// cameraview.cpp void CameraView::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event-mimeData()-hasFormat(application/x-camera-id)) { int cameraId event-mimeData()-data(application/x-camera-id).toInt(); QPointF dropPos event-scenePos(); // 计算最近的组边界框简化为矩形区域 int targetGroupId findNearestGroup(dropPos); if (targetGroupId 0) { // 1. 更新数据库 QSqlQuery query(m_db); query.prepare(UPDATE cameras SET group_id ? WHERE id ?); query.addBindValue(targetGroupId); query.addBindValue(cameraId); query.exec(); // 2. 重新加载该组设备图元局部刷新非全量重绘 reloadGroupItems(targetGroupId); } } }为什么不用QGraphicsItemGroup因为组是逻辑概念设备可能跨多个视图区域如地图缩放后用QGraphicsItemGroup会导致图元坐标计算混乱。此处采用“数据库驱动视图”模式拖拽结束即更新DB再触发视图局部重绘保证状态一致性。4. 避坑指南五个让开发者凌晨三点还在抓头发的典型问题这类QtWebEngine音视频混合项目坑不在代码逻辑而在环境、线程、坐标系的隐式耦合。以下是我在某跨平台系统集成中踩过的真坑按发生频率排序4.1 现象地图白屏控制台报Failed to load resource: net::ERR_FILE_NOT_FOUND原因index.html路径加载错误。工程默认用QUrl::fromLocalFile()加载但若index.html中引用了相对路径的CSS/JS如script srcjs/map.js而map.js实际在resources/子目录则路径解析失败。解决统一改为QUrl(qrc:///index.html)并将所有静态资源放入Qt资源系统.qrc文件确保路径固化。修改main.cpp中加载语句// 错误写法依赖文件系统路径 webView-load(QUrl::fromLocalFile(index.html)); // 正确写法资源系统路径 webView-load(QUrl(qrc:///index.html));4.2 现象点击“播放设备”后无视频但控制台有[AVHWDeviceContext] Cannot load libnvcuda.so原因libav试图启用CUDA硬件加速但Windows下未安装NVIDIA驱动或CUDA Toolkit导致解码器初始化失败自动降级为纯CPU解码却未报错最终帧队列为空。解决强制禁用硬件加速在AVPlayer初始化时添加// avplayer.cpp av_dict_set(opts, hwaccel, none, 0); // 关键 av_dict_set(opts, threads, 1, 0); // 避免多线程竞争QPainter4.3 现象设备图标拖拽后位置偏移缩放地图时图标“漂移”原因Qt Graphics View坐标系像素与百度地图坐标系经纬度未做实时映射。工程中CameraView::addCameraMarker()用固定偏移量放置图标但未监听地图zoom_end事件重算位置。解决在JS层监听缩放事件回调Qt重绘// index.html 中添加 map.addEventListener(zoomend, function() { var zoom map.getZoom(); window.qtWidget.onMapZoomChanged(zoom); });C侧接收后遍历所有设备图元调用item-setPos(mapPointToScene(lat, lng))重算像素坐标。4.4 现象SQLite数据库写入失败QSqlQuery::exec()返回false但lastError()为空原因数据库文件被其他进程如DB Browser for SQLite独占锁定。Qt的QSqlDatabase连接默认开启QSQLITE_OPEN_READWRITE若文件被占用则静默失败。解决添加连接前的文件锁检测// database.cpp QFile dbFile(camera.db); if (dbFile.exists() !dbFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) { qWarning() Database file is locked by another process; return false; }4.5 现象右键菜单“创建组”后新组不显示在左侧树形控件中原因QTreeWidget未设置setHeaderHidden(true)导致第一行被当作header占用新插入的QTreeWidgetItem实际显示在第二行视觉上“消失”。解决在mainwindow.cpp构造函数中显式隐藏headerui-groupTree-setHeaderHidden(true); // 并确保插入时用 addChild 而非 addTopLevelItem QTreeWidgetItem *groupItem new QTreeWidgetItem(); groupItem-setText(0, groupName); ui-groupTree-invisibleRootItem()-addChild(groupItem);5. 进阶技巧如何将此Demo快速适配到你自己的IPC设备与私有地图服务这个Demo的价值不仅在于“能跑”更在于它提供了一套可替换的抽象层。我把它拆成三个可插拔模块每次升级只需改其中一环不影响其他功能。5.1 替换视频源从RTSP到GB28181或海康EHome当前AVPlayer类硬编码RTSP协议但实际项目中常需对接国标设备。改造关键点是解耦解码器与数据源模块当前实现替换方案修改文件数据源avformat_open_input()实现IDataSource接口GB28181用ZLMediaKitSDKdatasource.h/cpp解码器avcodec_send_packet()保持不变只接收H.264/H.265 AnnexB NALUavplayer.cpp渲染QPainter::drawImage()可选改用QOpenGLWidget提升性能videowidget.cpp示例接入GB28181设备时IDataSource::start()启动ZLMediaKit的pullStream收到NALU后直接推给AVPlayer::decodeFrame()无需改动解码逻辑。5.2 替换地图服务从百度到高德或离线OSM百度地图密钥受限于域名内网部署易失效。切换高德只需改JS加载URL和坐标转换// index.html 中替换 // 百度 // script typetext/javascript srchttp://api.map.baidu.com/api?v2.0akYOUR_KEY/script // 高德需申请Key script srchttps://webapi.amap.com/maps?v1.4.15keyYOUR_AMAP_KEY/script // JS中坐标转换百度BD-09 → 高德GCJ-02 function bd09togcj02(bd_lon, bd_lat) { var x_pi 3.14159265358979324 * 3000.0 / 180.0; var x bd_lon - 0.0065; var y bd_lat - 0.006; var z Math.sqrt(x * x y * y) - 0.00002 * Math.sin(y * x_pi); var theta Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi); var gg_lng z * Math.cos(theta); var gg_lat z * Math.sin(theta); return [gg_lng, gg_lat]; }注意高德地图AMap.Marker的position参数是[lng, lat]数组与百度BMap.Point(lng, lat)一致无需改C调用逻辑。5.3 设备状态持久化增强从SQLite到MQTT状态同步当前设备启停状态只存本地DB无法反映真实IPC在线状态。加入MQTT可实现双向同步// mqttmanager.h class MqttManager : public QObject { Q_OBJECT public: void connectToBroker(const QString host, int port); void publishCameraStatus(int cameraId, bool online); // 发布设备在线状态 signals: void cameraStatusChanged(int cameraId, bool online); // 接收其他端状态变更 }; // 在AVPlayer播放成功时发布 void AVPlayer::onDecodeSuccess() { m_mqtt-publishCameraStatus(m_cameraId, true); } // 接收状态变更更新UI图标颜色 connect(m_mqtt, MqttManager::cameraStatusChanged, this, [](int id, bool online){ auto item findCameraItem(id); item-setPixmap(online ? m_onlinePixmap : m_offlinePixmap); });实践建议用Eclipse Mosquitto搭建轻量MQTT BrokerTopic设计为/cameras/{id}/statusQoS1保证消息必达。从那以后我每次接手新监控项目都会先用这个Demo搭出最小可行界面再按需替换视频源、地图、通信协议——它像一个精密的乐高底座上面的模块可以任意组合但底座的信号流、线程模型、坐标映射逻辑已经过千次验证。希望帮到你。本文还有配套的精品资源点击获取