1. 程式人生 > 其它 >Qt編寫安防視訊監控系統43-圖片回放

Qt編寫安防視訊監控系統43-圖片回放

尊重原創版權: https://www.gewuweb.com/hot/8786.html

Qt編寫安防視訊監控系統43-圖片回放

## 一、前言

之前就已經具備了本地回放、遠端回放、裝置播放3個模組,其中本地回放用來回放儲存在軟體本地電腦上的視訊檔案;遠端回放需要通過呼叫廠家sdk或者GB28181(沒實現,後期考慮增加)從NVR回放視訊;裝置播放通過通用的rtsp規則視訊流的形式呼叫nvr儲存錄影或者實時錄影回放;近期增加了個圖片回放模組,是用來回放圖片資料夾中的圖片檔案的,排隊繪製圖片序列,看起來有點視訊的感覺。

有一種場景是,使用者自己的人工智慧演算法,能夠拿到一堆圖片序列,自定義規則儲存,觸發報警後也拿到一堆圖片序列,這樣可以通過這個模組直接針對圖片序列進行播放,也支援匯入匯出,播放的速度可控制調節,拉動播放進度條直接定位到對應的圖片序列播放。

圖片儲存規則

- 預設儲存主目錄 image_normal。

- 拓展功能可以儲存對應的資料檔案比如警情文字和圖片檔案一個目錄 名稱一樣 拓展名可以是 txt。

## 二、功能特點

(一)軟體模組

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畫面輪詢,可設定輪詢分組(輪詢預案)、輪詢間隔、碼流型別等。

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.

5.
知乎主頁:https://www.zhihu.com/people/feiyangqingyun/

6.
線上文件:https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/

## 四、效果圖

## 五、核心程式碼

void frmVideoPlayImage::loadImage()
{
    int count = listFile.count();
    if (count == 0) {
        return;
    }

    //播放到末尾
    if (listIndex >= count) {
        //自動切換到下一個
        if (ui->listWidget->currentRow() < ui->listWidget->count() - 1) {
            ui->listWidget->setCurrentRow(ui->listWidget->currentRow() + 1);
            on_listWidget_doubleClicked();
        } else {
            isPlay = false;
            timerPlay->stop();
            ui->sliderPosition->setValue(0);
            ui->labTimePlay->setText(QString("第 %1 張").arg(0));
            ui->labTimeAll->setText(QString("共 %1 張").arg(0));
            return;
        }
    }

    //不斷的將圖片以訊號的形式發出去,對應關聯了槽函式會自動繪製
    QImage image(listFile.at(listIndex));
    emit receiveImage(image);
    listIndex++;

    ui->labTimePlay->setText(QString("第 %1 張").arg(listIndex));
    ui->sliderPosition->setValue(listIndex);
}

void frmVideoPlayImage::addItem(const QString &text, const QString &data)
{
    QListWidgetItem *item = new QListWidgetItem;
    item->setText(text);
    item->setData(Qt::UserRole, QString(data));
    item->setCheckState(Qt::Unchecked);
    ui->listWidget->addItem(item);
}

void frmVideoPlayImage::on_btnSelect_clicked()
{
    QDate dateStart = ui->dateStart->date();
    QDate dateEnd = ui->dateEnd->date();
    if (dateStart > dateEnd) {
        QUIHelper::showMessageBoxError("開始時間不能大於結束時間!", 3);
        return;
    }

    //將日期轉換為日期時間計算相差的天數,超過最大天數則提示不用繼續
    if (dateStart.daysTo(dateEnd) >= 60) {
        QUIHelper::showMessageBoxError("每次最大隻能查詢60天內!", 3);
        return;
    }

    ui->listWidget->clear();
    QString filePath;
    if (ui->cboxType->currentText() == "儲存圖片") {
        filePath = AppData::ImageNormalPath;
    } else {
        filePath = AppData::ImageAlarmPath;
    }

    //過濾指定的通道
    QString channel = ui->cboxCh->currentText();
    QString ch = QString("Ch%1").arg(channel.mid(2, channel.length()));

    //將有檔案的日期目錄載入到列表
    //並將該目錄對應的通道的圖片集合名稱存入自定義屬性
    //然後將開始時間加一天,直到大於結束時間
    while (dateStart <= dateEnd) {
        //生成對應日期的資料夾
        QString dateName = dateStart.toString("yyyy-MM-dd");
        QString savePath = QString("%1/%2").arg(filePath).arg(dateName);
        QDir saveDir(savePath);
        //判斷資料夾是否存在
        if (saveDir.exists()) {
            //指定檔案拓展名過濾,按照時間升序排序
            QStringList filter;
            filter << "*.jpg" << "*.png";
            QStringList fileNames = saveDir.entryList(filter, QDir::NoFilter, QDir::Time | QDir::Reversed);

            QStringList fullNames;
            foreach (QString fileName, fileNames) {
                QString name = savePath + "/" + fileName;
                if (channel == "所有通道") {
                    fullNames << name;
                } else {
                    if (fileName.startsWith(ch)) {
                        fullNames << name;
                    }
                }
            }

            int count = fullNames.count();
            if (count > 0) {
                QString text = QString("%1 %2 總數: %3").arg(dateName).arg(channel).arg(count);
                addItem(text, fullNames.join("|"));
            }
        }

        dateStart = dateStart.addDays(1);
    }

    ui->labTip->setText(QString("共找到 %1 個").arg(ui->listWidget->count()));
}

void frmVideoPlayImage::on_btnDown_clicked()
{
    //拿到所有勾選的檔案集合
    QStringList fileNames;
    int count = ui->listWidget->count();
    for (int row = 0; row < count; row++) {
        QListWidgetItem *item = ui->listWidget->item(row);
        if (item->checkState() == Qt::Checked) {
            fileNames << item->data(Qt::UserRole).toString().split("|");
        }
    }

    if (fileNames.count() == 0) {
        QUIHelper::showMessageBoxError("沒有選中任何檔案!", 3);
        return;
    }

    //彈出另存為對話方塊
    QString path = QUIHelper::getFolderName();
    if (!path.isEmpty()) {
        //挨個拷貝檔案
        foreach (QString fileName, fileNames) {
            QString name = fileName.split("/").last();
            QString targetFile = path + "/" + name;
            QFile::copy(fileName, targetFile);
        }

        DbQuery::addUserLog("拷貝圖片檔案");
        QUIHelper::showMessageBoxInfo("拷貝圖片檔案完成!", 3);
    }
}

QString frmVideoPlayImage::getHtml()
{
    //下面的圖片是存放在資原始檔中的示例圖片
    //真實應用改成圖片的絕對路徑
    QStringList images;
#if 1
    images << ":/image/liuyifei0.jpg" << ":/image/liuyifei2.jpg" << ":/image/liuyifei1.jpg";
    images << ":/image/liuyifei0.jpg" << ":/image/liuyifei1.jpg" << ":/image/liuyifei2.jpg";
    images << ":/image/liuyifei2.jpg" << ":/image/liuyifei1.jpg" << ":/image/liuyifei0.jpg";
#else
    //下面表示從可執行檔案目錄下的 snap 資料夾取圖片 1.jpg
    images << QUIHelper::appPath() + "/snap/1.jpg";
#endif

    //還有很多引數可以設定,可以查閱對應結構體
    UavsReportData reportData;
    reportData.images = images;
    reportData.title = "主標題";
    reportData.subTitle = "副標題";
    reportData.name = "無人機-2021";

    //獲取列印區域的尺寸用於計算圖片的寬度
    //多減一點是為了有點冗餘
    int imageWidth = (DataPrint::dataPrint->getPrinter()->pageRect(QPrinter::DevicePixel).width() - 80) / 2;

    QStringList list;
    DataCreat::creatUavsReportHead(list, reportData);
    DataCreat::creatUavsReportBody(list, reportData, imageWidth);
    return list.join("");
}

void frmVideoPlayImage::dataout(int type)
{
    DataContent dataContent;
    dataContent.fileName = QUIHelper::appPath() + "/db/html.pdf";
    dataContent.pageMargin = QMargins(15, 20, 15, 20);

    //初始化一次保證物件例項化成功
    DataHelper::Init();
    DataPrint::dataPrint->init();
    DataPrint::dataPrint->setDataContent(dataContent);

    //這裡重新設定html用到了列印區域所有需要設定一遍 setDataContent 後再重新設定
    //第一次 setDataContent 後才能獲取到準確的列印區域
    dataContent.html = getHtml();
    DataPrint::dataPrint->setDataContent(dataContent);

    if (type == 0) {
        DataPrint::dataPrint->print();
    } else if (type == 1) {
        DataPrint::dataPrint->open();
        DataPrint::dataPrint->close();
        QUIHelper::openFile(dataContent.fileName, "匯出告警報告");
    }
}

void frmVideoPlayImage::on_btnPdf_clicked()
{
    dataout(1);
}

void frmVideoPlayImage::on_btnPrint_clicked()
{
    dataout(0);
}

void frmVideoPlayImage::on_btnPlayVideo_clicked()
{
    if (ui->listWidget->currentRow() < 0) {
        return;
    }

    isPlay = !isPlay;
    if (isPlay) {
        IconHelper::setIcon(ui->btnPlayVideo, 0xf28d, btnRadius);
        timerPlay->start();
    } else {
        IconHelper::setIcon(ui->btnPlayVideo, 0xf144, btnRadius);
        timerPlay->stop();
    }
}

void frmVideoPlayImage::on_listWidget_doubleClicked()
{
    //從屬性中取出該資料夾對應的所有查詢到的圖片名稱集合
    listFile = ui->listWidget->currentItem()->data(Qt::UserRole).toString().split("|");
    int count = listFile.count();
    if (count > 0) {
        listIndex = 0;
        ui->labTimeAll->setText(QString("共 %1 張").arg(count));
        ui->sliderPosition->setRange(0, count - 1);
        ui->sliderPosition->setValue(0);

        isPlay = true;
        IconHelper::setIcon(ui->btnPlayVideo, 0xf28d, btnRadius);
        timerPlay->start();
    }
}

void frmVideoPlayImage::on_sliderFps_clicked()
{
    timerPlay->setInterval(ui->sliderPosition->value() * 100);
}

void frmVideoPlayImage::on_sliderFps_sliderMoved(int value)
{
    timerPlay->setInterval(value * 100);
}

void frmVideoPlayImage::on_sliderPosition_clicked()
{
    listIndex = ui->sliderPosition->value();
}

void frmVideoPlayImage::on_sliderPosition_sliderMoved(int value)
{
    listIndex = value;
}

void frmVideoPlayImage::on_listWidget_itemPressed(QListWidgetItem *item)
{
    bool checked = (item->checkState() == Qt::Checked);
    item->setCheckState(checked ? Qt::Unchecked : Qt::Checked);
}

更多內容參考: https://www.gewuweb.com/sitemap.html