基于OpenCV的远程人脸识别监控系统

基于OpenCV的人脸识别监控系统

作者:天津市大学软件学院 2333李成鑫

本项目已上传
Github:https://github.com/Programmer-Kenton/superviseClient

说明:开发此项目过程中碰到许多问题
除最后网络通信视频数据格式无法展示外均提供解决方案

项目开发流程

一、需求分析

使用uvc摄像头,实时获取视频,通过http协议传输视频数据,并实时显示到web浏览器或者PC客户端

  • 在ubuntu 系统架设视频服务器(mjpg-streamer ),实现抓取视频图像数据
  • 利用HTTP协议实现和视频服务器的数据交互,编写客户端程序(C++),向视频服务器发送获取视频的请求,并接收响应数据
  • 分析视频服务器的响应数据格式,从返回的数据中剥离出保存一帧图像,即一张JPEG格式的图片,获取的图片可以用图形查看工具直接打开
  • 使用QT的网络编程框架,向视频服务器发送获取视频或截图请求,并通过QT的信号和槽机制实时接收服务返回的响应数据,从中剥离出JPEG图像帧并显示到QT界面,通过不断刷新显示JPEG图像帧的方式,实现视频监控的客户端

通过浏览器查看视频监控画面

在这里插入图片描述

客户端查看监控画面(由于我这里出现网络传输视频格式出现问题,暂时画面传不上)

在这里插入图片描述

二、概要设计

远程视频监控系统结构图

在这里插入图片描述

三、详细设计

Mjpg-streamer简介

chatGPT给出的简介

在这里插入图片描述

Mjpg-streamer框架

在这里插入图片描述

通义灵码给出各个组件的解释

在这里插入图片描述

HTTP协议简介

【HTTP是什么?】https://www.bilibili.com/video/BV1zb4y127JU?vd_source=16c75c962f8e88b35892ccbe4254efa1

MJPG和JPEG图片简介

在这里插入图片描述

MJPEG图像格式标记

在这里插入图片描述

Qt视频监控客户端
  1. 创建QT应用程序与服务器建立连接

  2. 向服务器发送获取视频请求

    "GET/?action=stream=stream HTTP/1.1/\r\n\r\n"
    
  3. 接收服务器的响应数据

  4. 从JPEG图像帧显示到QT界面

  5. 重复以上动作,不断更新显示QT界面的图像帧,最终看到是视频流

四、编写代码

①在Ubuntu搭建服务器环境

需要使用的平台工具

Vmware、Ubuntu、mjpg_streamer、g++编译器、Cmake、远程连接工具Xshell、文件传输工具Xftp

Vmware链接

链接:https://pan.baidu.com/s/1vQpQ8DGfyAg92uj6EDWfSg
提取码:0dlh

Xshell&&Xftp链接

链接:https://pan.baidu.com/s/1jfqc1534Lu6nK3pjjDky1g
提取码:irul

Ubuntu镜像链接:

链接:https://pan.baidu.com/s/1AYTBjX-W4YCYYD2SeT-Ijg
提取码:xogp

mjpg_streamer百度云链接

链接:https://pan.baidu.com/s/1-pDDRYn7koq_stfDeWJzQA
提取码:0mcf

在这里插入图片描述

也可以使用笔者提供的Ubuntu虚拟机,注意mjpg_stream在/opt目录下

②安装mjpg_stream
1.开启虚拟机查看ip地址
kenton@kenton:~$ ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.65.128  netmask 255.255.255.0  broadcast 192.168.65.255
        inet6 fe80::20c:29ff:fec9:d67b  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:c9:d6:7b  txqueuelen 1000  (以太网)
        RX packets 215  bytes 21862 (21.8 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 108  bytes 16630 (16.6 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (本地环回)
        RX packets 6718  bytes 479865 (479.8 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6718  bytes 479865 (479.8 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

如果没有显示ens33 则执行dhclient ens33

root@kenton:~# dhclient ens33
Setting LLMNR support level "yes" for "2", but the global support level is "no".

dhclient会尝试联系DHCP服务器,获取新的IP地址、子网掩码、默认网关、DNS服务器等配置信息

2.使用Xftp传输文件至 /opt目录下并解压

在这里插入图片描述

注意:如果出现无法传输的情况,大概率是对opt文件没有读写的权限,或者你的22/23端口没有开放,试着换个端口

作者遇到的权限问题

chmod 777 /opt 赋予opt文件夹所有用户组下所有用户的所有权限

指定哪些用户类别(用户、组和其他)的哪些权限要增加、删除或设置

我们使用数字模式

0 没有权限 1 执行权限 2 写入权限 3 写入和执行权限 4 读取权限 5 读取和执行权限 6 读取和写入权限 7 读取、写入和执行权限

执行解压命令

tar -xvf mjpg-streamer-experimental.tar.gz -d /opt
-d 解压到指定目录 这里我们直接压opt 不写默认也是这里
3.重新编译mjpg-stream

进入mjpg-streamer-experimental/ 清空整个程序的内容

make clean
rm -rf_build

再执行make等待编译,如果中途出现缺少jpeglib.h则安装jpeg库后重新编译

make
apt-get install libjpeg-dev

如果没有make和gcc编译器则进行在线安装

 sudo apt update
 sudo apt install build-essential

测试图片插件编译

root@kenton:/opt/mjpg-streamer-experimental# clear
root@kenton:/opt/mjpg-streamer-experimental# ls
_build          docker-start.sh       makedeb.sh       mjpg_streamer.h         postinstall.sh  TODO
cmake           input_testpicture.so  Makefile         mjpg_streamer@.service  README.md       utils.c
CMakeLists.txt  input_uvc.so          mjpg_streamer    output_http.so          scripts         utils.h
Dockerfile      LICENSE               mjpg_streamer.c  plugins                 start.sh        www
root@kenton:/opt/mjpg-streamer-experimental# cd plugins/input_testpicture
root@kenton:/opt/mjpg-streamer-experimental/plugins/input_testpicture# ls
input_testpicture.c  input_testpicture.so  Makefile  pictures  tags  testpictures2.h  testpictures.h

再执行make clean和make
将编译好的input_testpicture.so复制到上级目录
cd input_testpicture.so ./
4.运行视频服务器
root@kenton:/opt/mjpg-streamer-experimental/plugins/input_testpicture# cd /opt/mjpg-streamer-experimental/
root@kenton:/opt/mjpg-streamer-experimental# ls
_build          docker-start.sh       makedeb.sh       mjpg_streamer.h         postinstall.sh  TODO
cmake           input_testpicture.so  Makefile         mjpg_streamer@.service  README.md       utils.c
CMakeLists.txt  input_uvc.so          mjpg_streamer    output_http.so          scripts         utils.h
Dockerfile      LICENSE               mjpg_streamer.c  plugins                 start.sh        www
root@kenton:/opt/mjpg-streamer-experimental# ./start.sh 
MJPG Streamer Version.: 2.0
 i: Using V4L2 device.: (null)
 i: Desired Resolution: 320 x 240
 i: Frames Per Second.: 10
 i: Format............: JPEG
 i: TV-Norm...........: DEFAULT
 i: init_VideoIn failed

出现init_VideoIn failed 说明虚拟机没有本机摄像头权限

在这里插入图片描述

在虚拟机->可移动设备->Quanta HP Wide Vision HD Camera->连接 即摄像头断开与主机的连接

由于作者的机器是HP所有这里显示HP,具体机器型号每个读者不同

按住Ctrl+D 打开虚拟机设置,把USB兼容性调为3.1

在这里插入图片描述

等待虚拟机重启后再次执行./start.sh指令

root@kenton:/opt/mjpg-streamer-experimental# ./start.sh 
MJPG Streamer Version.: 2.0
 i: Using V4L2 device.: /dev/video0
 i: Desired Resolution: 320 x 240
 i: Frames Per Second.: 10
 i: Format............: JPEG
 i: TV-Norm...........: DEFAULT
 i: FPS coerced ......: from 10 to 15
 o: www-folder-path......: ./www/
 o: HTTP TCP port........: 8080
 o: HTTP Listen Address..: (null)
 o: username:password....: disabled
 o: commands.............: enabled

打开虚拟机的火狐浏览器输入http://127.0.0.1:8080/static.html

或者在物理机打开浏览器输入网址,地址为虚拟机的IP,即可显示电脑前置视频监控

③搭建客户端开发环境
1.安装OpenCV

Releases - OpenCV 作者下载OpenCV-4.9.0版本 解放区特殊原因下载速度慢 可以魔法上网

2.重新编译构建OpenCV源码

注意这里我们使用Cmake作为构建工具,原始的OpenCV只提供MSVC版本的源码。所以我们需要再进行重新编译

作者使用MinGW编译器,我们打开下载好的OpenCV目录,在下面新建MinGW-build目录用于存放转换后的文件

在这里插入图片描述

需要用cmake-gui.exe工具进行转换

在这里插入图片描述

Where is the source code:选择原始OpenCV源码即source目录

Where to build the binaries:选择你重新构建后的源码存放路劲,作者是MinGW-build

之后点击configure 选择MinGW Makefiles和Specify naive compilers

即选择指定的生成器和本地编译构建

在这里插入图片描述

这里作者选择本地gcc和g++编译工具分别编译C/C++代码

在这里插入图片描述

等待转换完成后,由于本机没有安装Python解释器所以去掉一些模块再进行Generate,具体去除模块可以参照以下视频

【windows下使用cmake mingw源码编译opencv QT g++】https://www.bilibili.com/video/BV13o4y137YP?vd_source=16c75c962f8e88b35892ccbe4254efa1

在MinGW-build目录打开cmd窗口命令 执行mingw32-make -j4。

源码工程量巨大,编译十分耗时,这里j4指的是给mingw编译器四个线程更快速地编译

注意如果你没有安装Python解释器,同时没有去掉Python模块,这里的编译到20%左右就会崩掉,作者因为这个被困了一个下午,此外确保你已经配好的mingw编译器的环境变量

在这里插入图片描述

编译完成显示100% 再进行mingw32-make install命令生成install文件夹

3.配置OpenCV环境变量

在这里插入图片描述

在生成的install\x64\mingw\bin配置环境变量

在这里插入图片描述

测试配置 在cmd窗口输入Opencv_version

在这里插入图片描述

4.配置CLion开发环境

选择编译器和调试器

在这里插入图片描述

在这里插入图片描述

配置CMakeLists.txt

# 设置CMake的最低版本要求为3.27
cmake_minimum_required(VERSION 3.27)
# 定义项目名称为superviseClient
project(superviseClient)

# 设置C++标准为C++17
set(CMAKE_CXX_STANDARD 17)
#开启CMake的AUTOMOC、AUTORCC和AUTOUIC功能,用于自动处理Qt的MOC(Meta-Object Compiler)、资源文件和UI文件
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

# 设置CMake的搜索路径,以找到Qt 6和OpenCV的安装位置
set(CMAKE_PREFIX_PATH "E:/QT/6.5.3/mingw_64")
set(OpenCV_DIR "E:/OpenCV/opencv/MinGW-build/install")

# 使用find_package命令寻找Qt 6的组件(Core、Gui、Widgets、Network)和OpenCV库,要求必须找到
find_package(Qt6 COMPONENTS
        Core
        Gui
        Widgets
        Network
        REQUIRED)

find_package(
        OpenCV
        REQUIRED
)

# 添加OpenCV头文件的搜索路径
include_directories(${OpenCV_INCLUDE_DIRS})

qt_add_resources(
        haarcascade_eye.xml
        haarcascade_eye_tree_eyeglasses.xml
        haarcascade_frontalcatface.xml
        haarcascade_frontalcatface_extended.xml
        haarcascade_frontalface_alt.xml
        haarcascade_frontalface_alt2.xml
        haarcascade_frontalface_alt_tree.xml
        haarcascade_frontalface_default.xml
        haarcascade_fullbody.xml
        haarcascade_lefteye_2splits.xml
        haarcascade_license_plate_rus_16stages.xml
        haarcascade_lowerbody.xml
        haarcascade_profileface.xml
        haarcascade_righteye_2splits.xml
        haarcascade_russian_plate_number.xml
        haarcascade_smile.xml
        haarcascade_upperbody.xml
)

add_executable(
        superviseClient main.cpp
        head/superviseClientWidget.h
        head/FaceDetection.h
        source/superviseClientWidget.cpp
        source/FaceDetection.cpp
        ui/superviseClientWidget.ui
)

# 链接superviseClient所需的Qt库和OpenCV库
target_link_libraries(superviseClient
        Qt::Core
        Qt::Gui
        Qt::Widgets
        Qt::Network
        ${OpenCV_LIBS}
)

if (WIN32 AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
    set(DEBUG_SUFFIX)
    if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")
        set(DEBUG_SUFFIX "d")
    endif ()
    set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
    if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
        set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
        if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
            set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
        endif ()
    endif ()
    if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E make_directory
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy
                "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
    endif ()
    foreach (QT_LIB Core Gui Widgets Network)
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy
                "${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll"
                "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
    endforeach (QT_LIB)
endif ()

Cmake构建成功在External Libraries出现OpeCV库成功被导入

在这里插入图片描述

④编写人脸识别接口代码
头文件
/**
 * @Description 人脸识别接口 头文件
 * @Version 1.0.0
 * @Date 2024/5/18 15:47
 * @Github https://github.com/Programmer-Kenton
 * @Author Kenton
 */
#ifndef SUPERVISECLIENT_FACEDETECTION_H
#define SUPERVISECLIENT_FACEDETECTION_H

#include "QObject"
#include "opencv2/opencv.hpp"
#include "iostream"
#include "QImage"
#include "QDebug"

// 使用OpenCV的命名空间
using namespace cv;


class FaceDetection : public QObject{
    Q_OBJECT
public:
    explicit FaceDetection(QObject *parent = nullptr);

public slots:
    // 人脸识别
    void onFaceDetection(QImage img);

};


#endif //SUPERVISECLIENT_FACEDETECTION_H
源文件
/**
 * @Description 人脸识别接口 源文件
 * @Version 1.0.0
 * @Date 2024/5/18 15:47
 * @Github https://github.com/Programmer-Kenton
 * @Author Kenton
 */
#include "../head/FaceDetection.h"

FaceDetection::FaceDetection(QObject *parent) : QObject(parent) {

}

void FaceDetection::onFaceDetection(QImage img) {
    // 将图像转成为RGB24位
    QImage image = img.convertToFormat(QImage::Format_RGB888);
    // 将QImage格式转换成Mat格式
    Mat matImg = Mat(image.height(), image.width(), CV_8UC3,image.bits(),image.bytesPerLine());
    // 将图片进行灰度处理
    Mat imgGray;
    cvtColor(matImg,imgGray, COLOR_RGB2GRAY);
    std::cout << cvtColor << std::endl;
    // 加载分类器
    CascadeClassifier faceDetector;
    std::string detectPath = "../resource/haarcascade_frontalface_default.xml";
    faceDetector.load(detectPath);
    std::vector<Rect> faces;
    // 直方图均衡化
    equalizeHist(imgGray,imgGray);
    // 多尺寸检测人脸
    faceDetector.detectMultiScale(imgGray,faces,1.1,3,0,Size(30,30));
    if (!faces.empty()){
        QString str = "检测到有:" + QString::number(faces.size()) + "人进入监控区域";
        qDebug() << str;
    }
}
⑤编写窗体的UI文件

窗体的所需的控件、控件布局、控件命名

在这里插入图片描述

在这里插入图片描述

⑥编写窗体代码
头文件
/**
 * @Description 人脸识别远程监控的客户端窗体头文件
 * @Version 1.0.0
 * @Date 2024/5/18 15:31
 * @Github https://github.com/Programmer-Kenton
 * @Author Kenton
 */
#ifndef SUPERVISECLIENT_SUPERVISECLIENTWIDGET_H
#define SUPERVISECLIENT_SUPERVISECLIENTWIDGET_H

#include <QWidget>
#include "QVector"
#include "QLabel"
#include "QNetworkAccessManager"
#include "QNetworkReply"
#include "QUrl"
#include "QThread"
#include "../head/FaceDetection.h"

QT_BEGIN_NAMESPACE
namespace Ui { class superviseClientWidget; }
QT_END_NAMESPACE

class superviseClientWidget : public QWidget {
Q_OBJECT

public:
    explicit superviseClientWidget(QWidget *parent = nullptr);

    ~superviseClientWidget() override;

signals:
    // 剥离图片帧后发送此信号进行人脸检测
    void imageAcquired(QImage);

private slots:

    // 接收到视频流数据 从中剥离一张图片帧
    void onMjpgReadyRead();

    // 获取快照
    void onJpegReadyRead();

    // 获取视频按钮
    void on_btnGetMjpg_clicked();

    // 获取快照按钮
    void on_btnGetJpeg_clicked();

private:
    Ui::superviseClientWidget *ui;

    // 管理整个通信过程
    QNetworkAccessManager *manager;

    // 根据URL生成请求包
    QNetworkRequest request;

    // 获取视频流响应数据包
    QNetworkReply *mjpgReply;

    // 保存视频流响应数据的缓冲区
    QByteArray mjpgBuffer;

    // 保存从视频流中截取的图片
    QImage mjpgImg;

    // 获取快照响应数据包
    QNetworkReply *jpegReply;

    // 保存快照的缓冲区
    QByteArray jpegBuffer;

    // 保存快照图片
    QImage jpegImg;

    // 人脸检测工作线程
    QThread *thread;

    // 人脸检测工作对象
    FaceDetection *detectWork;

    // 保存显示的快照的label
    QVector<QLabel *> labelJpegs;

    // 索引
    int m_index;

};


#endif //SUPERVISECLIENT_SUPERVISECLIENTWIDGET_H
源文件
/**
 * @Description 人脸识别远程监控的客户端窗体源文件
 * @Version 1.0.0
 * @Date 2024/5/18 15:31
 * @Github https://github.com/Programmer-Kenton
 * @Author Kenton
 */
// You may need to build the project (run Qt uic code generator) to get "ui_superviseClientWidget.h" resolved

#include "../head/superviseClientWidget.h"
#include "../ui/ui_superviseClientWidget.h"


superviseClientWidget::superviseClientWidget(QWidget *parent) :
        QWidget(parent), ui(new Ui::superviseClientWidget) {
    ui->setupUi(this);

    // 设置窗体大小不可变
    setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);

    // 将Label放入容器
    labelJpegs.append(ui->labelJpeg1);
    labelJpegs.append(ui->labelJpeg2);
    labelJpegs.append(ui->labelJpeg3);
    labelJpegs.append(ui->labelJpeg4);

    // 网络管理类对象
    manager = new QNetworkAccessManager(this);
    thread = new QThread(this);
    // 移动线程 不能指定父对象
    detectWork = new FaceDetection;
    // 将工作对象移动到thread线程负责
    detectWork->moveToThread(thread);
    connect(this, SIGNAL(imageAcquired(QImage)),detectWork, SLOT(onFaceDetection(QImage)));
    thread->start();
}

superviseClientWidget::~superviseClientWidget() {
    delete manager;
    manager = nullptr;
    thread->terminate();
    thread->wait();
    delete ui;
}

void superviseClientWidget::onMjpgReadyRead() {
    // 1.接收数据
    mjpgBuffer += mjpgReply->readAll();
    // 2.定义变量保存图片帧的起始标记 开始标记:0xff 0xd8 结束标记:0xff 0xd9
    // 开始标记
    char startFlag[3] = {(char)0xff,(char)0xd8};
    // 结束标记
    char endFlag[3] = {(char)0xff,(char)0xd9};
    // 3.查找发送来的数据流中的图片的起始标记
    // 从0开始查找图片的开始标记 找到返回索引 找不到返回-1
    int startIndex = mjpgBuffer.indexOf(startFlag,0);
    std::cout << "startIndex:" << startIndex << std::endl;
    if(startIndex == -1){
        // 没有找到起始标记
        qDebug() << "没有找到标记";
        mjpgBuffer.clear();
        return;
    }
    // 从开始标记位置向后查找结束标记的位置
    int endIndex = mjpgBuffer.indexOf(endFlag,startIndex);
    if(endIndex == -1){
        // 没有找到结束标记 图片数据还没有接收完毕
        return;
    }
    // 4.视频流中截取一张图片数据
    QByteArray imgBuffer = mjpgBuffer.mid(startIndex,(endIndex - startIndex) + 2);
    // 5.将QByteArray的数据加载到图片QImage中 图片格式位JPG
    bool ret = mjpgImg.loadFromData(imgBuffer,"JPG");
    if (ret == false){
        qDebug() << "图片加载失败";
        mjpgBuffer.clear();
        return;
    }else{
        // 图片加载成功 显示到labelMjpg上
        ui->labelMjpg->setPixmap(QPixmap::fromImage(mjpgImg));
        // 发射信号 进行人脸检测
        emit imageAcquired(mjpgImg);
        mjpgBuffer.clear();
    }

//
//    // 1.接收数据
//    mjpgBuffer += mjpgReply->readAll();
//
//    // 2.定义变量保存图片帧的起始标记和结束标记
//    const QByteArray startFlag = QByteArray::fromHex("FFD8");
//    const QByteArray endFlag = QByteArray::fromHex("FFD9");
//
//    // 3.查找图片的起始标记
//    int startIndex = mjpgBuffer.indexOf(startFlag);
//    std::cout << "startIndex: " << startIndex << std::endl;
//
//    if (startIndex == -1) {
//        // 没有找到起始标记
//        qDebug() << "没有找到起始标记";
//        return;
//    }
//
//    // 4.查找结束标记
//    int endIndex = mjpgBuffer.indexOf(endFlag, startIndex);
//
//    if (endIndex == -1) {
//        // 没有找到结束标记,图片数据还没有接收完毕
//        return;
//    }
//
//    // 5.从视频流中截取一张图片数据
//    QByteArray imgBuffer = mjpgBuffer.mid(startIndex, (endIndex - startIndex) + endFlag.size());
//
//    // 6.将QByteArray的数据加载到图片QImage中,图片格式为JPG
//    if (!mjpgImg.loadFromData(imgBuffer, "JPG")) {
//        qDebug() << "图片加载失败";
//        mjpgBuffer.clear();
//        return;
//    }
//
//    // 7.图片加载成功,显示到labelMjpg上
//    ui->labelMjpg->setPixmap(QPixmap::fromImage(mjpgImg));
//
//    // 8.发射信号进行人脸检测
//    emit imageAcquired(mjpgImg);
//
//    // 9.清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + endFlag.size());



// 1.接收数据
//    mjpgBuffer += mjpgReply->readAll();
//
//    // 2.定义变量保存图片帧的起始标记和结束标记
//    const QByteArray startFlag = QByteArray::fromHex("FFD8");
//    const QByteArray endFlag = QByteArray::fromHex("FFD9");
//
//    // 调试输出缓冲区内容(仅前100字节用于调试)
//    qDebug() << "mjpgBuffer:" << mjpgBuffer.left(100).toHex();
//
//    // 3.查找图片的起始标记
//    int startIndex = mjpgBuffer.indexOf(startFlag);
//    std::cout << "startIndex: " << startIndex << std::endl;
//
//    if (startIndex == -1) {
//        // 没有找到起始标记
//        qDebug() << "没有找到起始标记";
//        return;
//    }
//
//    // 4.查找结束标记
//    int endIndex = mjpgBuffer.indexOf(endFlag, startIndex);
//
//    if (endIndex == -1) {
//        // 没有找到结束标记,图片数据还没有接收完毕
//        return;
//    }
//
//    // 5.从视频流中截取一张图片数据
//    QByteArray imgBuffer = mjpgBuffer.mid(startIndex, (endIndex - startIndex) + endFlag.size());
//
//    // 6.将QByteArray的数据加载到图片QImage中,图片格式为JPG
//    if (!mjpgImg.loadFromData(imgBuffer, "JPG")) {
//        qDebug() << "图片加载失败";
//        mjpgBuffer.clear();
//        return;
//    }
//
//    // 7.图片加载成功,显示到labelMjpg上
//    ui->labelMjpg->setPixmap(QPixmap::fromImage(mjpgImg));
//
//    // 8.发射信号进行人脸检测
//    emit imageAcquired(mjpgImg);
//
//    // 9.清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + endFlag.size());


//    // 1.接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // 调试输出缓冲区内容(仅前100字节用于调试)
//    qDebug() << "Received data:" << newData.toHex();
//    qDebug() << "Current buffer:" << mjpgBuffer.left(100).toHex();
//
//    // 2.定义变量保存图片帧的起始标记和结束标记
//    const QByteArray startFlag = QByteArray::fromHex("FFD8");
//    const QByteArray endFlag = QByteArray::fromHex("FFD9");
//
//    // 3.查找图片的起始标记
//    int startIndex = mjpgBuffer.indexOf(startFlag);
//    std::cout << "startIndex: " << startIndex << std::endl;
//
//    if (startIndex == -1) {
//        // 没有找到起始标记
//        qDebug() << "没有找到起始标记";
//        return;
//    }
//
//    // 4.查找结束标记
//    int endIndex = mjpgBuffer.indexOf(endFlag, startIndex);
//
//    if (endIndex == -1) {
//        // 没有找到结束标记,图片数据还没有接收完毕
//        return;
//    }
//
//    // 5.从视频流中截取一张图片数据
//    QByteArray imgBuffer = mjpgBuffer.mid(startIndex, (endIndex - startIndex) + endFlag.size());
//
//    // 调试输出图像数据内容(仅前100字节用于调试)
//    qDebug() << "Extracted image buffer:" << imgBuffer.left(100).toHex();
//
//    // 6.将QByteArray的数据加载到图片QImage中,图片格式为JPG
//    if (!mjpgImg.loadFromData(imgBuffer, "JPG")) {
//        qDebug() << "图片加载失败";
//        mjpgBuffer.remove(0, endIndex + endFlag.size());  // 清除已处理的数据以避免缓冲区过大
//        return;
//    }
//
//    // 7.图片加载成功,显示到labelMjpg上
//    ui->labelMjpg->setPixmap(QPixmap::fromImage(mjpgImg));
//
//    // 8.发射信号进行人脸检测
//    emit imageAcquired(mjpgImg);
//
//    // 9.清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + endFlag.size());

// 1.接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // 调试输出缓冲区内容(仅前100字节用于调试)
//    qDebug() << "Received data:" << newData.toHex();
//    qDebug() << "Current buffer:" << mjpgBuffer.left(100).toHex();
//
//    // 检查数据是否以FFD8开头(JPEG图像的起始标记)
//    if (mjpgBuffer.startsWith(QByteArray::fromHex("FFD8"))) {
//        qDebug() << "Received data starts with JPEG start marker (FFD8)";
//    }

// AVI格式
//    // 接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // AVI格式的起始和结束标志
//    const QByteArray aviStartFlag = QByteArray::fromHex("52494646"); // "RIFF"
//    const QByteArray aviEndFlag = QByteArray::fromHex("00000000");
//
//    // 查找起始标志
//    int startIndex = mjpgBuffer.indexOf(aviStartFlag);
//    if (startIndex == -1) {
//        // 没有找到起始标志
//        qDebug() << "没有找到起始标志";
//        return;
//    }
//
//    // 查找结束标志
//    int endIndex = mjpgBuffer.indexOf(aviEndFlag, startIndex);
//    if (endIndex == -1) {
//        // 没有找到结束标志,视频数据还没有接收完毕
//        return;
//    }
//
//    // 从视频流中截取一段数据,这里以起始标志为开头,结束标志为结尾
//    QByteArray videoData = mjpgBuffer.mid(startIndex, endIndex + aviEndFlag.size());
//
//    // 处理视频数据,例如保存到文件或进行解码等操作
//    // processVideoData(videoData);
//
//    // 清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + aviEndFlag.size());


// MP4格式
 接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // MP4 格式的起始和结束标志
//    const QByteArray mp4StartFlag = QByteArray::fromHex("000000186674797069736F6D"); // "....ftypisom"
//    const QByteArray mp4EndFlag = QByteArray::fromHex("00000000");
//
//    // 查找起始标志
//    int startIndex = mjpgBuffer.indexOf(mp4StartFlag);
//    if (startIndex == -1) {
//        // 没有找到起始标志
//        qDebug() << "没有找到起始标志";
//        return;
//    }
//
//    // 查找结束标志
//    int endIndex = mjpgBuffer.indexOf(mp4EndFlag, startIndex);
//    if (endIndex == -1) {
//        // 没有找到结束标志,视频数据还没有接收完毕
//        return;
//    }
//
//    // 从视频流中截取一段数据,这里以起始标志为开头,结束标志为结尾
//    QByteArray videoData = mjpgBuffer.mid(startIndex, endIndex + mp4EndFlag.size());
//
//    // 处理视频数据,例如保存到文件或进行解码等操作
//    // processVideoData(videoData);
//
//    // 清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + mp4EndFlag.size());

 MOV格式
//    // 接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // MOV 格式的起始和结束标志
//    const QByteArray movStartFlag = QByteArray::fromHex("00000014667479706D703432"); // "....ftypmp42"
//    const QByteArray movEndFlag = QByteArray::fromHex("00000000");
//
//    // 查找起始标志
//    int startIndex = mjpgBuffer.indexOf(movStartFlag);
//    if (startIndex == -1) {
//        // 没有找到起始标志
//        qDebug() << "没有找到起始标志";
//        return;
//    }
//
//    // 查找结束标志
//    int endIndex = mjpgBuffer.indexOf(movEndFlag, startIndex);
//    if (endIndex == -1) {
//        // 没有找到结束标志,视频数据还没有接收完毕
//        return;
//    }
//
//    // 从视频流中截取一段数据,这里以起始标志为开头,结束标志为结尾
//    QByteArray videoData = mjpgBuffer.mid(startIndex, endIndex + movEndFlag.size());
//
//    // 处理视频数据,例如保存到文件或进行解码等操作
//    // processVideoData(videoData);
//
//    // 清除已处理的数据
//    mjpgBuffer.remove(0, endIndex + movEndFlag.size());



///*
// * 数据丢失或截断:在您的代码中,您假设了图像的起始标记位于数据流的开头,这意味着您只在数据的开头查找起始标记。
// * 如果数据流中的起始标记之前有数据丢失或者数据截断,您可能会错过起始标记。建议在查找起始标记时从数据流的当前位置开始搜索。
// * */
//
//    // 1.接收数据
//    QByteArray newData = mjpgReply->readAll();
//    mjpgBuffer += newData;
//
//    // 2.定义变量保存图片帧的起始标记和结束标记
//    const QByteArray startFlag = QByteArray::fromHex("FFD8");
//    const QByteArray endFlag = QByteArray::fromHex("FFD9");
//
//    // 3.调试输出缓冲区内容(仅前100字节用于调试)
//    qDebug() << "Received data:" << newData.toHex();
//    qDebug() << "Current buffer:" << mjpgBuffer.left(100).toHex();
//
//    // 4.查找图片的起始标记
//    int startIndex = mjpgBuffer.indexOf(startFlag);
//    qDebug() << "startIndex: " << startIndex;
//
//    while (startIndex != -1) {
//        // 5.查找结束标记
//        int endIndex = mjpgBuffer.indexOf(endFlag, startIndex);
//        if (endIndex == -1) {
//            // 没有找到结束标记,等待更多数据
//            return;
//        }
//
//        // 6.从视频流中截取一张图片数据
//        QByteArray imgBuffer = mjpgBuffer.mid(startIndex, (endIndex - startIndex) + endFlag.size());
//
//        // 7.将QByteArray的数据加载到图片QImage中,图片格式为JPG
//        QImage img;
//        if (!img.loadFromData(imgBuffer, "JPG")) {
//            qDebug() << "图片加载失败";
//        } else {
//            // 8.图片加载成功,显示到labelMjpg上
//            ui->labelMjpg->setPixmap(QPixmap::fromImage(img));
//            // 9.发射信号进行人脸检测
//            emit imageAcquired(img);
//        }
//
//        // 10.清除已处理的数据
//        mjpgBuffer = mjpgBuffer.mid(endIndex + endFlag.size());
//
//        // 11.继续查找下一个起始标记
//        startIndex = mjpgBuffer.indexOf(startFlag);
//    }

}

void superviseClientWidget::on_btnGetMjpg_clicked() {
    // 1.准备请求包 设置URL地址 根据URL自动生成请求包
    request.setUrl(QUrl("http://192.168.65.128:8080?action=stream"));
    // 2.发送请求包 使用Get方式 请求获取视频流数据 返回专门用于接收视频流数据的响应对象
    mjpgReply = manager->get(request);
    // 3.当服务器发送来数据时 触发readyRead()信号的发射
    connect(mjpgReply, SIGNAL(readyRead()),this, SLOT(onMjpgReadyRead()));
}

void superviseClientWidget::on_btnGetJpeg_clicked() {
    // 1.准备请求包 设置url地址 根据url地址自动生成请求包
    request.setUrl(QUrl("http://192.168.65.128:8080?action=snapshot"));
    // 2.发送请求包 使用Get方法请求获取快照数据 返回专门用于接收快照数据的响应对象
    jpegReply = manager->get(request);
    // 3.服务器发来数据触发readyRead()信号的发射
    connect(jpegReply, SIGNAL(readyRead()),this, SLOT(onJpegReadyRead()));
}

void superviseClientWidget::onJpegReadyRead() {
    // 1.接收数据
    jpegBuffer += jpegReply->readAll();
    // 2.定义变量保存图片帧的起始标记 开始标记:0xff 0xd8 结束标记:0xff 0xd9
    // 开始标记
    char startFlag[3] = {(char)0xff,(char)0xd8};
    char endFlag[3] = {(char)0xff,(char)0xd9};
    // 3.查找发送来的数据流中的图片的起始标记
    // 从0开始查找图片的开始标记 找到返回索引 找不到返回-1
    int startIndex = jpegBuffer.indexOf(startFlag,0);
    // 没有找到
    if(startIndex == -1){
        jpegBuffer.clear();
        return;
    }
    // 从开始标记位置向后查找结束标记的位置
    int endIndex = jpegBuffer.indexOf(endFlag,startIndex);
    // 找到开始标记 但没有找到结束标记位置:图片数据还没有接收完毕
    if (endIndex == -1){
        return;
    }
    // 4.从快照数据流中截取一张图片数据
    QByteArray imgBuffer = jpegBuffer.mid(startIndex,(endIndex - startIndex) + 2);
    // 5.将QByteArray的数据加载到图片QImage中 图片格式位JPG
    bool ret = jpegImg.loadFromData(imgBuffer,"JPG");
    if (ret == false){
        jpegBuffer.clear();
        return;
    } else{
        // 加载图片成功 放入在labelJpeg容器的label中
        labelJpegs[m_index]->setPixmap(QPixmap::fromImage(
                jpegImg.scaled(labelJpegs[m_index]->size(),Qt::KeepAspectRatio)));
        m_index = (++m_index) % 4;
        jpegBuffer.clear();
    }
}

在这里插入图片描述

网络数据流传输正常,初步判断服务器reply回来的数据格式不是JPEG类型的,因此不适用这些标记,开始标记:0xff 0xd8 结束标记:0xff 0xd9

作者比较傻,一开始怀疑可能是mjpp_stream传输了别的格式视频,因此询问chatGPT各个视频格式的标记特点进行,根据其数据流特点进行逐个阶段都没有通过

在这里插入图片描述

等作者解决此问题就及时更新,感谢您的观看~

最后放上WeChat欢迎大家来找作者愉快玩耍🎈
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值