基于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视频监控客户端
-
创建QT应用程序与服务器建立连接
-
向服务器发送获取视频请求
"GET/?action=stream=stream HTTP/1.1/\r\n\r\n"
-
接收服务器的响应数据
-
从JPEG图像帧显示到QT界面
-
重复以上动作,不断更新显示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欢迎大家来找作者愉快玩耍🎈