1. 程式人生 > 其它 >Qt編寫安防視訊監控系統36-onvif連續移動

Qt編寫安防視訊監控系統36-onvif連續移動

一、前言

時隔一年多,重新對視訊監控系統的onvif核心重寫,一方面為了相容Qt6,一方面按功能分類提高效率。整體邏輯思路是一樣的,主要的改動是由於Qt6不再支援QtXmlPatterns模組(其實這個模組在Qt5的後面的版本也逐漸提示為廢棄模組),onvif協議通訊中的資料都是帶有名稱空間的xml資料,用QtXmlPatterns模組去解析是最合適的,現在全部改成了用最原始最基礎的QtXml模組去解析,畢竟QtXml模組肯定是一直在的,這是相當基礎的模組,無論以後Qt7還是Qt100肯定都會有。

之前雲臺控制這塊,已經實現了相對移動和絕對移動,後面在測試了幾十種廠家的攝像機以後,發現有部分攝像機廠家對onvif協議支援不完全,打通了部分協議,比如雲臺控制這塊就只支援連續移動,其實真的場景也絕大部分都是需要連續移動的,看起來相當絲滑,之前在沒有實現連續移動命令的時候,雲臺控制預設用的是相對移動,為了實現連續移動的效果,搞了個定時器來不斷執行命令模擬連續移動,效果不是很理想,看起來怪怪的,有時候停頓,近期才發現原來有連續移動的指令,這就舒服了,絲滑到爆,按下對應的方位觸發連續移動,鬆開自動停止。

onvif主要的功能

  1. 搜尋裝置,獲取裝置的資訊比如廠家、型號等。
  2. 獲取裝置的多個配置檔案資訊profile。
  3. 獲取對應配置檔案的視訊流地址rtsp,以及解析度等引數。
  4. 雲臺控制,上下左右移動,焦距放大縮小,相對和絕對移動。
  5. 獲取預置位資訊,觸發預置位。
  6. 訂閱事件,接收裝置的各種訊息尤其是報警事件比如IO口的報警。
  7. 抓圖,獲取裝置當前的圖片。
  8. 獲取、建立、刪除使用者資訊。
  9. 獲取和裝置網路配置資訊比如IP地址等。
  10. 獲取和設定NTP時間同步。
  11. 獲取和設定裝置時間。
  12. 重啟裝置。

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。

二、功能特點

(一)軟體模組

  1. 視訊監控模組,各種停靠小窗體子模組,包括裝置列表、圖文警情、視窗資訊、雲臺控制、預置位、巡航設定、裝置控制、懸浮地圖、網頁瀏覽等。
  2. 視訊回放模組,包括本地回放、遠端回放、裝置播放、圖片回放、視訊上傳等。
  3. 電子地圖模組,包括圖片地圖、線上地圖、離線地圖、路徑規劃等。
  4. 日誌查詢模組,包括本地日誌、裝置日誌等。
  5. 系統設定模組,包括系統設定(基本設定、視訊引數、資料庫設定、地圖配置、串列埠配置等)、錄影機管理、攝像機管理、輪詢配置、使用者管理等。

(二)基礎功能

  1. 支援各種視訊流(rtsp、rtmp、http等)、視訊檔案(mp4、rmvb、avi等)、本地USB攝像機播放。
  2. 支援多畫面切換,包括1、4、6、8、9、13、16、25、36、64畫面切換。
  3. 支援全屏切換,多種切換方式包括滑鼠右鍵選單、工具欄按鈕、快捷鍵(alt+enter全屏,esc退出全屏)。
  4. 支援視訊輪詢,包括1、4、9、16畫面輪詢,可設定輪詢分組(輪詢預案)、輪詢間隔、碼流型別等。
  5. 支援onvif協議,包括裝置搜尋、雲臺控制、裝置控制(圖片引數、校對時間、系統重啟,抓拍圖片等)。
  6. 支援許可權管理,不同的使用者可以對應不同的模組許可權,比如刪除日誌、關閉系統等。
  7. 資料庫支援多種,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金倉等。
  8. 本地USB攝像機支援設定解析度、幀率等引數。
  9. 所有停靠模組都自動生成對應的選單用來控制顯示和隱藏,在標題欄右鍵可以彈出。
  10. 支援顯示所有模組、隱藏所有模組、復位普通佈局、復位全屏佈局。
  11. 雙擊裝置彈出實時預覽視訊,支援圖片地圖、線上地圖、離線地圖等。
  12. 攝像機節點拖曳到對應窗體播放視訊,同時支援拖曳本地檔案直接播放。
  13. 刪除視訊支援滑鼠右鍵刪除、懸浮條關閉刪除、拖曳到視訊監控面板外刪除等多種方式。
  14. 圖片地圖上裝置按鈕可自由拖動,自動儲存位置資訊。百度地圖上可以滑鼠單擊獲取經緯度資訊,用來更新裝置位置。
  15. 視訊監控面板窗體中任意通道支援拖曳交換,瞬間響應。
  16. 封裝了百度地圖,檢視切換,運動軌跡,裝置點位,滑鼠按下獲取經緯度等。
  17. 雙擊節點、拖曳節點、拖曳窗體交換位置等操作,均自動更新儲存最後的播放地址,下次軟體開啟自動應用。
  18. 右下角音量條控制元件,失去焦點自動隱藏,音量條帶靜音圖示。
  19. 支援視訊截圖,可指定單個或者對所有通道截圖,底部小工具欄也有截圖按鈕。
  20. 支援超時自動隱藏滑鼠指標、自動全屏機制。
  21. 支援onvif雲臺控制,可上下左右移動雲臺攝像機,包括復位和焦距調整等。
  22. 支援任意onvif攝像機,包括但不限於海康、大華、宇視、天地偉業、華為等。
  23. 可儲存視訊,可選定時儲存或者單檔案儲存,可選儲存間隔時間。
  24. 可設定視訊流通訊方式tcp+udp,可設定視訊解碼是速度優先、質量優先、均衡等。
  25. 可設定軟體中文名稱、英文名稱、LOGO圖示等。
  26. 儲存的視訊檔案支援匯出到指定目錄,支援批量上傳到伺服器。

(三)特色功能

  1. 主介面採用停靠窗體模式,各種元件以小模組的形式加入,可自定義任意模組加入。
  2. 停靠模組可拖動任意位置嵌入和懸浮,支援最大化全屏,支援多螢幕。
  3. 雙重佈局檔案儲存機制,正常模式、全屏模式都對應不同的佈局方案,自動切換和儲存,比如全屏模式可以突出幾個模組透明顯示在指定位置,更具科幻感現代化。
  4. 原創onvif協議機制,採用底層協議解析(udp廣播搜尋+http請求執行命令)更輕量易懂易學習拓展,不依賴任何第三方元件比如gsoap。
  5. 原創資料匯入匯出機制,跨平臺不依賴任何元件,瞬間匯出資料。
  6. 內建多個原創元件,宇宙超值超級牛逼,包括資料匯入匯出元件(匯出到xls、pdf、列印)、資料庫元件(資料庫管理執行緒、自動清理資料執行緒、萬能分頁、資料請求等)、地圖元件、視訊監控元件、檔案多執行緒收發元件、onvif通訊元件、通用瀏覽器核心元件等。
  7. 自定義資訊框+錯誤框+詢問框+右下角提示框(包含多種格式)等。
  8. 精美換膚,高達17套面板樣式隨意更換,所有樣式全部統一,包括選單等。
  9. 視訊控制元件懸浮條可以自行增加多個按鈕,監控介面底部小工具欄也可自行增加按鈕。
  10. 雙擊攝像機節點自動播放視訊,雙擊節點自動依次新增視訊,會自動跳到下一個,雙擊父節點自動新增該節點下的所有視訊。可選主碼流、子碼流。
  11. 錄影機管理、攝像機管理,可新增刪除修改匯入匯出列印資訊,立即應用新的裝置資訊生成樹狀列表,不需重啟。
  12. 可選多種核心自由切換,ffmpeg、vlc、mpv等,均可在pro中設定。推薦用ffmpeg,跨平臺最多,預設提供好了linux和mac平臺上編譯好的庫。
  13. 支援硬解碼,可設定硬解碼型別(qsv、dxva2、d3d11va等)。
  14. 預設採用opengl繪製視訊,超低的CPU資源佔用,支援yuyv和nv12兩種格式繪製,很牛逼。
  15. 高度可定製化,使用者可以很方便的在此基礎上衍生自己的功能,比如增加自定義模組,增加執行模式、機器人監控、無人機監控、挖掘機監控等。
  16. 支援xp、win7、win10、linux、mac、各種國產系統(UOS、中標麒麟、銀河麒麟等)、嵌入式linux等系統。
  17. 註釋完整,專案結構清晰,超級詳細完整的使用開發手冊,精確到每個程式碼檔案的功能說明,不斷持續迭代版本。

三、體驗地址

  1. 體驗地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 檔名:bin_video_system.zip。
  2. 國內站點:https://gitee.com/feiyangqingyun
  3. 國際站點:https://github.com/feiyangqingyun
  4. 個人主頁:https://blog.csdn.net/feiyangqingyun
  5. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/

四、效果圖

五、核心程式碼

#include "frmipcptz.h"
#include "ui_frmipcptz.h"
#include "quihelper.h"
#include "devicehelper.h"
#include "deviceonvif.h"

frmIpcPtz::frmIpcPtz(QWidget *parent) : QWidget(parent), ui(new Ui::frmIpcPtz)
{
    ui->setupUi(this);
    this->initForm();
    this->initIcon();
}

frmIpcPtz::~frmIpcPtz()
{
    delete ui;
}

bool frmIpcPtz::eventFilter(QObject *watched, QEvent *event)
{
    //滑鼠按下指定鬆開自動停止
    if (event->type() == QEvent::MouseButtonPress) {
        int type = 255;
        if (watched == ui->btnPtzZoomIn) {
            type = 9;
        } else if (watched == ui->btnPtzZoomOut) {
            type = 10;
        } else if (watched == ui->btnPtzFocusIn) {
            type = 11;
        } else if (watched == ui->btnPtzFocusOut) {
            type = 12;
        } else if (watched == ui->btnPtzApertureIn) {
            type = 13;
        } else if (watched == ui->btnPtzApertureOut) {
            type = 14;
        }

        QMetaObject::invokeMethod(this, "ptzControl", Qt::QueuedConnection, Q_ARG(int, type));
    } else if (event->type() == QEvent::MouseButtonRelease) {
        QMetaObject::invokeMethod(this, "ptzControl", Qt::QueuedConnection, Q_ARG(int, 255));
    }

    return QWidget::eventFilter(watched, event);
}

void frmIpcPtz::initForm()
{
    //安裝事件過濾器識別鬆開自動停止
    ui->btnPtzZoomIn->installEventFilter(this);
    ui->btnPtzZoomOut->installEventFilter(this);

    //繫結雲臺儀表盤按下和鬆開事件
    ui->gaugeCloud->setAutoRepeat(false);
    connect(ui->gaugeCloud, SIGNAL(mousePressed(int, QString)), this, SLOT(mousePressed(int, QString)));
    connect(ui->gaugeCloud, SIGNAL(mouseReleased(int, QString)), this, SLOT(mouseReleased(int, QString)));

    //關聯樣式改變訊號自動重新設定圖示
    connect(AppEvent::Instance(), SIGNAL(changeStyle()), this, SLOT(initIcon()));
}

void frmIpcPtz::initIcon()
{
    //設定圖形字型
    int fontSize = 15;
    IconHelper::setIcon(ui->btnPtzZoomIn, 0xf067, fontSize);
    IconHelper::setIcon(ui->btnPtzZoomOut, 0xf068, fontSize);
    IconHelper::setIcon(ui->btnPtzFocusIn, 0xf067, fontSize);
    IconHelper::setIcon(ui->btnPtzFocusOut, 0xf068, fontSize);
    IconHelper::setIcon(ui->btnPtzApertureIn, 0xf067, fontSize);
    IconHelper::setIcon(ui->btnPtzApertureOut, 0xf068, fontSize);
}

void frmIpcPtz::mousePressed(int position, const QString &strPosition)
{
    DeviceHelper::addMsg(QString("按下雲臺 %1").arg(strPosition));
    QMetaObject::invokeMethod(this, "ptzControl", Qt::QueuedConnection, Q_ARG(int, position));
}

void frmIpcPtz::mouseReleased(int position, const QString &strPosition)
{
    DeviceHelper::addMsg(QString("鬆開雲臺 %1").arg(strPosition));
    QMetaObject::invokeMethod(this, "ptzControl", Qt::QueuedConnection, Q_ARG(int, 255));
}

void frmIpcPtz::ptzControl(int position)
{
    //計算速度,轉為小數
    double step = (double)ui->sliderPtzStep->value() / 10;

    //根據按下的不同部位傳送雲臺控制命令
    //1. x、y、z 範圍都在0-1之間。
    //2. x為負數,表示左轉,x為正數,表示右轉。
    //3. y為負數,表示下轉,y為正數,表示上轉。
    //4. z為正數,表示拉近,z為負數,表示拉遠。
    //5. 通過x和y的組合,來實現雲臺的控制。
    //6. 通過z的組合,來實現焦距控制。

    //0-8依次表示底部/左下角/左側/左上角/頂部/右上角/右側/右下角/中間
    if (position == 0) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, 0.0, -step, 0.0);
    } else if (position == 1) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, -step, -step, 0.0);
    } else if (position == 2) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, -step, 0.0, 0.0);
    } else if (position == 3) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, -step, step, 0.0);
    } else if (position == 4) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, 0.0, step, 0.0);
    } else if (position == 5) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, step, step, 0.0);
    } else if (position == 6) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, step, 0.0, 0.0);
    } else if (position == 7) {
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, step, -step, 0.0);
    } else if (position == 8) {
        //先停止再復位
        DeviceOnvif::ptzControl(0, AppData::CurrentUrl, 0.0, 0.0, 0.0);
        //DeviceOnvif::ptzControl(1, AppData::CurrentUrl, 0.0, 0.0, 0.0);
    } else if (position == 9) {
        //變倍+
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, 0.0, 0.0, step);
    } else if (position == 10) {
        //變倍-
        DeviceOnvif::ptzControl(3, AppData::CurrentUrl, 0.0, 0.0, -step);
    } else if (position == 11) {
        //變焦+
    } else if (position == 12) {
        //變焦-
    } else if (position == 13) {
        //光圈+
    } else if (position == 14) {
        //光圈-
    } else {
        //停止
        DeviceOnvif::ptzControl(0, AppData::CurrentUrl, 0.0, 0.0, 0.0);
    }
}