Qt編寫安防視頻監控系統18-雲臺控制

一、前言

雲臺控制是視頻監控系統中必備的一個功能,對球機進行上下左右的移動,還有焦距的控制,其實核心就是控制XYZ三個座標軸,為了開發這個模塊,特意研究了各種雲臺控制的方法和開源庫比如soap,有些廠家使用自家SDK控制雲臺,但是大部分都會選擇onvif來控制,畢竟是國際標準的通用的,只要符合這個標準的都可以使用,onvif協議的解析通常用的開源庫是soap,涵蓋的內容比較全,包括獲取各種設備信息和回控等,缺點就是比較臃腫,使用非常不容易,函數名實在是有點不順手,很多新手都繞在其中不知所措最後放棄,其實onvif官方提供的就是soap,可能要照顧到所有的onvif標準吧,內容特別多,我看過其中的部分源碼,底層機制和我最終自創的解析機制完全一致,為此特意將純Qt網絡通信封裝了一個onvif通信類做成的pri模塊,大致的處理流程如下:

**onvif處理流程**

1. 綁定組播IP(239.255.255.250)和端口(3702),發送固定的xml格式的數據搜索設備。

2. 接收到的xml格式的數據解析,得到設備的Onvif地址。

3. 對Onvif地址發送對應的數據,收到數據取出對應的節點數據。

4. 請求Onvif地址獲取Media地址和Ptz地址,Media地址用來獲取詳細的配置文件,Ptz地址用來雲臺控制。

5. ptz控制是對Ptz地址發送對應的數據即可。

6. 設置了用戶認證的需要組織用戶token信息一塊發送,每次都需要作鑑權處理。

7. 接收到的數據不是標準的xml數據,沒法按照正常的節點解析來處理,只能用QXmlQuery來做。

8. 每個廠家設備返回的數據未必完全一致,基本上都不一致,需要進行模糊查找節點值。

9. 特意採用底層協議解析,因為soap太臃腫函數名稱太另類,特意做的輕量級的。

10. 兩個必備工具,Onvif Device Manager 和 Onvif Device Test Tool。

**ptz雲臺說明**

1. x、y、z 範圍都在0-1之間。

2. x為負數,表示左轉,x為正數,表示右轉。

3. y為負數,表示下轉,y為正數,表示上轉。

4. z為正數,表示拉近,z為負數,表示拉遠。

5. 通過x和y的組合,來實現雲臺的控制。

6. 通過z的組合,來實現焦距控制。

**onvif功能模塊特點**

1. 廣播搜索設備,支持IPC和NVR,依次返回,可選擇不同的網卡IP。

2. 依次獲取Onvif地址、Media地址、Profile文件、Rtsp地址。

3. 可對指定的Profile獲取視頻流Rtsp地址,比如主碼流子碼流地址。

4. 可對每個設備設置Onvif用戶信息,用於認證獲取詳細信息。

5. 可實時預覽攝像機圖像。

6. 支持雲臺控制,可上下左右調節雲臺,支持絕對移動和相對移動,可放到和縮小圖像遠近。

7. 支持Qt4和Qt5任意Qt版本,親測Qt4.7.0到Qt5.12.4。

8. 支持任意編譯器,親測mingw、msvc、gcc、clang。

9. 支持任意操作系統,親測xp、win7、win10、linux、嵌入式linux、樹莓派全志H3等。

10. 支持任意Onvif攝像機和NVR,親測海康、大華、宇視、華為、海思芯片內核等,可定製開發。

11. 支持對指定IP地址進行單播搜索,比如跨網段情況下非常有用。

12. 純Qt編寫,超級小巧輕量,總共約2000行代碼,不依賴任何第三方的庫和組件,跨平臺。

13. 封裝好了通用的數據發送和接收解析的函數,可以非常方便的自行拓展其他Onvif處理比如修改IP等。

14. 工具上提供了收發數據文本框,顯示收發的數據,方便查看和分析。

15. 支持所有Onvif設備,代碼工整,接口友好,直接引入pri即可使用。

通用視頻控件開源地址:[https://gitee.com/feiyangqingyun/QWidgetDemo](https://gitee.com/feiyangqingyun/QWidgetDemo) [https://github.com/feiyangqingyun/QWidgetDemo](https://github.com/feiyangqingyun/QWidgetDemo)

文件名稱:videowidget

體驗地址:[https://gitee.com/feiyangqingyun/QWidgetExe](https://gitee.com/feiyangqingyun/QWidgetExe) [https://github.com/feiyangqingyun/QWidgetExe](https://github.com/feiyangqingyun/QWidgetExe)

文件名稱:bin_video_system.zip

二、功能特點

1. 支持16畫面切換,全屏切換等,包括1+4+6+8+9+13+16畫面切換。

2. 支持alt+enter全屏,esc退出全屏。

3. 自定義信息框+錯誤框+詢問框+右下角提示框。

4. 17套皮膚樣式隨意更換,所有樣式全部統一,包括菜單等。

5. 雲臺儀表盤鼠標移上去高亮,八個方位精準識別。

6. 底部畫面工具欄(畫面分割切換+截圖聲音等設置)移上去高亮。

7. 可在配置文件更改左上角logo+中文軟件名稱+英文軟件名稱。

8. 封裝了百度地圖,三維切換,設備點位,鼠標按下獲取經緯度等。

9. 堆棧窗體,每個窗體都是個單獨的qwidget,方便編寫自己的代碼。

10. 頂部鼠標右鍵菜單,可動態控制時間CPU+左上角面板+左下角面板+右上角面板+右下角面板的顯示和隱藏,支持恢復默認佈局。

11. 工具欄可以放置多個小圖標和關閉圖標。

12. 左側右側可拖動拉伸,並自動記憶寬高位置,重啟後恢復。

13. 雙擊攝像機節點自動播放視頻,雙擊節點自動依次添加視頻,會自動跳到下一個,雙擊父節點自動添加該節點下的所有視頻。

14. 攝像機節點拖曳到對應窗體播放視頻,同時支持拖曳本地文件直接播放。

15. 視頻畫面窗體支持拖曳交換,瞬間響應。

16. 雙擊節點+拖曳節點+拖曳窗體交換位置,均自動更新url.txt。

17. 支持從url.txt中加載16通道視頻播放,自動記憶最後通道對應的視頻,軟件啟動後自動打開播放。

18. 右下角音量條控件,失去焦點自動隱藏,音量條帶靜音圖標。

19. 集成百度地圖,可以添加設備對應位置,自動生成地圖,支持縮放和三維地圖,提供地圖風格選擇,共12種風格。

20. 視頻拖動到通道窗體外自動刪除視頻。

21. 鼠標右鍵可刪除當前+所有視頻,截圖當前+所有視頻。

22. 錄像機管理、攝像機管理,可添加刪除修改導入導出打印信息,立即應用新的設備信息生成樹狀列表,不需重啟。

23. 在pro文件中可以自由開啟是否加載地圖。

24. 視頻播放可選四種內核自由切換,vlc+ffmpeg+easyplayer+海康sdk,均可在pro中設置。

25. 可設置1+4+9+16畫面輪詢,可設置輪詢間隔以及輪詢碼流類型等,直接在主界面底部工具欄右側單擊啟動輪詢按鈕即可,再次單擊停止輪詢。

26. 默認超過10秒鐘未操作自動隱藏鼠標指針。

27. 支持onvif搜素設備,支持任意onvif攝像機,包括但不限於海康大華宇視天地偉業華為等,支持onvif雲臺控制。

28. 高度可定製化,用戶可以很方便的在此基礎上衍生自己的功能,支持linux系統。

三、效果圖


Qt編寫安防視頻監控系統18-雲臺控制


Qt編寫安防視頻監控系統18-雲臺控制


四、核心代碼

<code>OnvifDevice *frmVideoMain::getCurrentDevice()
{
OnvifDevice *onvifDevice = 0;

//判斷當前url,找出該url對應的ptz地址
if (!App::CurrentUrl.isEmpty()) {
//可能是主碼流也可能是子碼流
int index1 = DBData::IpcInfo_RtspMain.indexOf(App::CurrentUrl);
int index2 = DBData::IpcInfo_RtspSub.indexOf(App::CurrentUrl);
int index = -1;
if (index1 >= 0) {
index = index1;
} else if (index2 >= 0) {
index = index2;
}

if (index >= 0) {
QString userName = DBData::IpcInfo_UserName.at(index);
QString userPwd = DBData::IpcInfo_UserPwd.at(index);
QString onvifAddr = DBData::IpcInfo_OnvifAddr.at(index);
QString mediaAddr = DBData::IpcInfo_MediaAddr.at(index);
QString ptzAddr = DBData::IpcInfo_PtzAddr.at(index);

bool exist = false;
foreach (OnvifDevice *device, devices) {
if (device->getDeviceUrl() == onvifAddr) {
exist = true;
onvifDevice = device;
break;;
}
}

if (!exist) {
onvifDevice = new OnvifDevice(this);
}

onvifDevice->setUser(userName, userPwd);
onvifDevice->setDeviceUrl(onvifAddr);
onvifDevice->setMediaUrl(mediaAddr);
onvifDevice->setPtzUrl(ptzAddr);


if (!exist) {
devices << onvifDevice;
}
}
}

return onvifDevice;
}

void frmVideoMain::moveRelative(double x, double y, double z)
{
OnvifDevice *device = getCurrentDevice();
if (device != 0) {
QString profileToken = device->getProfile();
device->moveRelative(profileToken, x, y, z);
qDebug() << "相對移動" << App::CurrentUrl << profileToken;
}
}

void frmVideoMain::moveAbsolute(double x, double y, double z)
{
OnvifDevice *device = getCurrentDevice();
if (device != 0) {
QString profileToken = device->getProfile();
device->moveAbsolute(profileToken, x, y, z);
qDebug() << "絕對移動" << App::CurrentUrl << profileToken;
}
}

void frmVideoMain::mousePressed(int position)
{
QString str;
if (position == 0) {
str = "底部";
} else if (position == 1) {
str = "左下角";
} else if (position == 2) {
str = "左側";
} else if (position == 3) {
str = "左上角";
} else if (position == 4) {
str = "頂部";
} else if (position == 5) {
str = "右上角";
} else if (position == 6) {
str = "右側";
} else if (position == 7) {

str = "右下角";
} else if (position == 8) {
str = "中間";
}

DeviceHelper::addMsg(QString("按下雲臺 %1").arg(str));
}

void frmVideoMain::mouseReleased(int position)
{
QString str;
if (position == 0) {
str = "底部";
} else if (position == 1) {
str = "左下角";
} else if (position == 2) {
str = "左側";
} else if (position == 3) {
str = "左上角";
} else if (position == 4) {
str = "頂部";
} else if (position == 5) {
str = "右上角";
} else if (position == 6) {
str = "右側";
} else if (position == 7) {
str = "右下角";
} else if (position == 8) {
str = "中間";
}

DeviceHelper::addMsg(QString("鬆開雲臺 %1").arg(str));
mousePtz(position);
}

void frmVideoMain::mousePtz(int position)
{
//根據按下的不同部位發送雲臺控制命令
//1. x、y、z 範圍都在0-1之間。
//2. x為負數,表示左轉,x為正數,表示右轉。
//3. y為負數,表示下轉,y為正數,表示上轉。

//4. z為正數,表示拉近,z為負數,表示拉遠。
//5. 通過x和y的組合,來實現雲臺的控制。
//6. 通過z的組合,來實現焦距控制。

//計算速度,轉為小數
double speed = (double)ui->sliderPtzSpeed->value() / 10;
if (position == 0) {
moveRelative(0.0, -speed, 0.0);
} else if (position == 1) {
moveRelative(-speed, -speed, 0.0);
} else if (position == 2) {
moveRelative(-speed, 0.0, 0.0);
} else if (position == 3) {
moveRelative(-speed, speed, 0.0);
} else if (position == 4) {
moveRelative(0.0, speed, 0.0);
} else if (position == 5) {
moveRelative(speed, speed, 0.0);
} else if (position == 6) {
moveRelative(speed, 0.0, 0.0);
} else if (position == 7) {
moveRelative(speed, -speed, 0.0);
} else if (position == 8) {
moveAbsolute(0.0, 0.0, 0.0);
}
}/<code>


分享到:


相關文章: