c++qt开发第三天 摄像头采集视频


capture_thread.h


一、这个文件是干嘛的?(一句话先懂)

👉这是一个用 Qt 的QThread写的“视频采集线程类”

作用大致是:

  • Linux 摄像头设备/dev/video1采集视频

  • 把采集到的图像转换成QImage

  • 通过Qt 信号发给界面显示 / 通过 UDP 广播发送


二、头文件结构总览(先有整体)

#ifndef CAPTURE_THREAD_H #define CAPTURE_THREAD_H // 各种头文件 // 宏定义 // 结构体 // CaptureThread 类 #endif

这是标准C/C++ 头文件保护,防止重复包含,不用纠结


三、头文件为什么这么多?(分三类)

① Linux 底层相关(摄像头 / 显存)

#include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/videodev2.h>

👉 用来干这些事:

头文件用途
fcntl.h打开设备文件
unistd.hread / write
videodev2.hV4L2 摄像头接口
sys/mman.h内存映射(mmap)

📌结论

这是直接操作 Linux 摄像头设备,不是 OpenCV 那种高级接口。


② Qt 相关(线程 / 图片 / 网络)

#include <QThread> #include <QImage> #include <QUdpSocket>

👉 用途:

Qt 类干嘛
QThread创建线程
QImage保存一帧图像
QUdpSocketUDP 广播视频

四、宏定义(很关键)

#define VIDEO_DEV "/dev/video1" #define FB_DEV "/dev/fb0" #define VIDEO_BUFFER_COUNT 3

含义:

意义
/dev/video1摄像头设备
/dev/fb0LCD 显存
3摄像头缓冲区数量

📌V4L2 常规做法:用 3 个 buffer 做循环缓冲


五、结构体:buffer_info

struct buffer_info { void *start; unsigned int length; };

👉 这是V4L2 的缓冲区描述结构

成员含义
start缓冲区首地址
length缓冲区大小

📌一帧图像 = 一个 buffer


六、重点来了:CaptureThread

1️⃣ 继承关系

class CaptureThread : public QThread

👉这是一个线程类

📌 在 Qt 中:

  • QThread= 一个独立执行的线程

  • 真正线程代码写在run()


2️⃣Q_OBJECT是干嘛的?

Q_OBJECT

👉启用 Qt 的“信号-槽机制”

没有它:

  • signals

  • slots
    全部不能用


七、signals(信号)——线程往外“喊话”

signals: void imageReady(QImage); void sendImage(QImage);

怎么理解?

👉线程干完活 → 告诉 UI

信号用途
imageReady(QImage)通知界面:有新图像
sendImage(QImage)用于网络发送

📌信号 ≈ “我这里准备好数据了!”


八、private 成员变量(控制线程行为)

bool startFlag = false; bool startBroadcast = false; bool startLocalDisplay = false;

含义一看就懂版:

变量作用
startFlag是否开启采集线程
startBroadcast是否 UDP 广播
startLocalDisplay是否本地显示

👉线程内部会根据这些 flag 决定做不做事


线程函数

void run() override;

📌重点重点重点
这里面一般是:

while(startFlag) { 采集一帧; 转 QImage; emit imageReady(img); }

九、public 构造函数(很简单)

CaptureThread(QObject *parent = nullptr) { Q_UNUSED(parent); }

👉 只是为了:

  • 和 Qt 体系兼容

  • 没用parent,防止警告


十、slots(槽函数)——外部“控制线程”

1️⃣ 开启 / 关闭线程

void setThreadStart(bool start)
逻辑拆开看:
startFlag = start;
  • true→ 开始采集

  • false→ 停止采集

if (start) { if (!this->isRunning()) this->start(); }

👉只有线程没跑,才启动

else { this->quit(); }

👉请求线程退出


2️⃣ 开关广播

void setBroadcast(bool start)

👉 只是改标志位


3️⃣ 开关本地显示

void setLocalDisplay(bool start)

👉 同样只是改标志位


十一、整张图一句话理解(非常重要)

UI 线程
👉 调用setThreadStart(true)
👉 采集线程run()开始跑
👉 摄像头采集一帧
👉 转成QImage
👉emit imageReady(img)
👉 UI 显示图像


capture_thread.cpp

好,这个capture_thread.cpp我给你按“一步一步跑起来”的顺序讲,你把它当成
👉“Linux 摄像头 + Qt 线程 + 信号槽 + UDP 广播”的完整实战模板来看。

你不用一次全懂,看完这一遍,至少知道每一段在干嘛


一、run()是什么地位?(先定性)

void CaptureThread::run()

👉这是线程真正执行的函数

在 Qt 里:

  • start()→ 系统自动调用run()

  • run()里面就是子线程逻辑

  • 不能写 UI,只能发信号


二、最外层的宏判断(为什么有)

#ifdef linux #ifndef __arm__ return; #endif

含义:

条件结果
Linux + ARM✅ 真正执行
Linux + x86❌ 直接 return
Windows❌ 不编译

📌原因
这是给 正点原子 ARM 开发板 用的,PC 上没有/dev/video1


三、第一阶段:打开摄像头设备

video_fd = open(VIDEO_DEV, O_RDWR);
  • VIDEO_DEV=/dev/video1

  • 成功 → 返回文件描述符

  • 失败 → 打印错误并退出线程

👉Linux 里,摄像头 = 文件


四、第二阶段:设置摄像头参数(格式)

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;

意思一句话:

我要一个640×480、RGB565 格式的摄像头图像

然后:

ioctl(video_fd, VIDIOC_S_FMT, &fmt);

👉把参数“塞进”摄像头驱动


五、第三阶段:申请缓冲区(V4L2 核心)

req_bufs.count = 3; req_bufs.memory = V4L2_MEMORY_MMAP;

👉告诉驱动:我要 3 个帧缓冲,用 mmap 方式

ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs);

📌这一步只是“申请”,还没拿到地址


六、第四阶段:mmap 映射缓冲区(重点)

ioctl(video_fd, VIDIOC_QUERYBUF, &buf);

👉 查询第n_buf个 buffer 的信息

bufs_info[n_buf].start = mmap(...);

👉把“摄像头内部缓冲区”映射到用户空间

📌 结果是:

摄像头DMA → 内核buffer → mmap → 用户指针

七、第五阶段:把缓冲区“还给驱动”

ioctl(video_fd, VIDIOC_QBUF, &buf);

👉 意思是:

“我准备好了,你可以往这个 buffer 里放数据了”


八、第六阶段:启动摄像头采集

ioctl(video_fd, VIDIOC_STREAMON, &type);

📌摄像头开始工作


九、第七阶段:真正的“视频循环”(核心)

while (startFlag)

👉 只要外部没关线程,就一直采集


1️⃣ 取出一帧(DQBUF)

ioctl(video_fd, VIDIOC_DQBUF, &buf);

👉驱动说:这一帧你可以用了


2️⃣ 把 raw 数据 → Qt 的 QImage(关键桥梁)

QImage qImage( (unsigned char*)bufs_info[n_buf].start, 640, 480, QImage::Format_RGB16 );

📌这一步非常重要:

  • 摄像头给的是RGB565

  • Qt 用QImage::Format_RGB16

  • 没有拷贝数据,只是“包装”


3️⃣ 本地显示(信号槽)

if (startLocalDisplay) emit imageReady(qImage);

👉 子线程不能直接操作 UI

👉 用信号通知主线程

emit imageReady → QLabel::setPixmap

4️⃣ UDP 广播(图像发网络)

if (startBroadcast)

步骤拆解:

① 创建 UDP 套接字
QUdpSocket udpSocket;
② QImage → JPEG → QByteArray
qImage.save(&buff, "JPEG");

👉 原始 RGB 太大 → 压缩成 JPEG

③ Base64(防止乱码)
QByteArray base64Byte = byte.toBase64();
④ 广播发送
udpSocket.writeDatagram( base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888 );

📌这就是“视频服务器”的核心


5️⃣ 用完后,再放回驱动(QBUF)

ioctl(video_fd, VIDIOC_QBUF, &buf);

👉 形成循环缓冲


十、线程结束后的清理

munmap(...) close(video_fd);

👉 释放内存 + 关闭设备


十一、整段代码一句话总结(非常重要)

这个线程做的事就是:
👉 摄像头取一帧
👉 包装成 QImage
👉 要么发给 UI 显示
👉 要么压缩后 UDP 广播
👉 再把 buffer 还给驱动


信号槽 + 线程调用关系图

太好了 👍
这一块一旦想通,Qt 线程 + 信号槽你就算“过关”了

下面我给你不用 Qt 术语、用工程思维来讲,再配一张“脑中必须有的调用关系图”


一、先给你一句“定理级结论”

Qt 中:

  • run()里的代码在子线程

  • UI 永远只能在主线程

  • 子线程 → UI =只能靠信号槽


二、三种“角色”,先分清(非常重要)

┌────────────┐ │ 主线程(UI) │ ← QLabel / QPushButton / 界面 └─────┬──────┘ │ 信号槽 ┌─────▼──────┐ │ CaptureThread │ ← QThread 对象(壳) └─────┬──────┘ │ run() ┌─────▼──────┐ │ 子线程执行体 │ ← 摄像头采集 └────────────┘

📌重点
CaptureThread 对象属于主线程
run()里的代码运行在子线程


三、从“按钮点击”开始看完整流程

① 你在 UI 点了「开始采集」

connect(button, &QPushButton::clicked, captureThread, &CaptureThread::setThreadStart);

👉发生在:主线程


② 调用槽函数setThreadStart(true)

void setThreadStart(bool start) { startFlag = start; if (start) { if (!isRunning()) start(); // 关键! } }

📌重点来了

函数在哪个线程
setThreadStart()主线程
start()主线程
run()子线程

start()干了什么?(很多人卡在这)

this->start();

👉它不会执行 run()

👉 它只是:

向操作系统申请一个新线程
成功后,OS 自动调用 run()


四、run()才是真正的“子线程世界”

void CaptureThread::run() { while (startFlag) { 采集一帧; emit imageReady(qImage); } }

📌从这里开始:

  • 不在 UI 线程

  • 不能碰 QLabel / QPushButton

  • 只能emit


五、emit imageReady(qImage)到底发生了什么?

这是最关键的一步


1️⃣ 你在 main.cpp / widget.cpp 里一般会这样连

connect(captureThread, &CaptureThread::imageReady, this, &Widget::updateImage);

📌 连接时:

  • captureThread→ 子线程发信号

  • this (Widget)→ 主线程对象


2️⃣ Qt 自动判断“跨线程连接”

信号发出线程 ≠ 槽函数对象线程

Qt 自动使用:

Qt::QueuedConnection

3️⃣ 这意味着什么?(非常重要)

子线程 emit imageReady ↓ Qt 把 QImage 放进 主线程事件队列 ↓ 主线程空闲时 ↓ 调用 updateImage(QImage)

👉子线程不会等
👉UI 不会卡死


六、用一张“时间线图”彻底看懂

时间 → ──────────────────────────────────────────── 主线程: [按钮点击] ↓ setThreadStart(true) ↓ start() ↓ (继续跑UI) 子线程: run()开始 ↓ 摄像头采集 ↓ emit imageReady ↓ (立刻继续采集) 主线程: Qt事件循环 ↓ updateImage() ↓ QLabel显示

📌关键理解

emit ≠ 直接调用
emit = “发消息”


七、为什么 UI 不会崩?(考试必问)

❌ 错误做法(会崩):

// 子线程里 ui->label->setPixmap(...); ❌

✅ 正确做法(你现在这个):

emit imageReady(qImage); ✅

👉 UI 操作100% 在主线程


八、你代码里的三种“信号控制关系”

信号 / 槽控制方向
setThreadStart()UI → 子线程
setLocalDisplay()UI → 子线程
imageReady()子线程 → UI

📌非常标准的生产级写法


九、用“单片机思维”类比(给你兜底)

如果你把 Qt 当成 MCU:

QtMCU
主线程main()
子线程定时器中断
signal中断置位
slot中断回调

👉中断不能直接改 LCD,只能发标志位


十、你现在应该“通”的 3 个点

如果你点头,说明真的懂了:

1️⃣CaptureThread 对象在主线程,但 run 在子线程
2️⃣emit 只是投递消息,不是函数调用
3️⃣跨线程信号槽 = 自动队列


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1124999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零构建:手写一个支持“高度定制化排版”的 Chrome 网页摘录插件

在碎片化阅读时代&#xff0c;我们经常需要将网页上的精彩段落保存下来&#xff0c;整理成 PDF 或 EPUB 放入电子书阅读器中。现有的插件&#xff08;如 Evernote、Pocket&#xff09;虽然强大&#xff0c;但往往存在两个痛点&#xff1a; 格式混乱&#xff1a;直接抓取网页 H…

晶台光耦在储能系统中的作用

光耦在储能系统中通过电气隔离实现高压与低压电路的安全隔离&#xff0c;是保障系统稳定运行的关键器件。晶台光耦KL1018/KL1019系列采用LSOP4封装&#xff0c;具备5KV隔离电压及-55~110℃宽温特性&#xff0c;广泛应用于太阳能储能逆变器。其红外发射二极管与光电晶体管耦合结…

基于时间和空间的大规模电动汽车入网网损调度 建立MISOCP模型,分时段优化,并行计算(实时优化)

基于时间和空间的大规模电动汽车入网网损调度 建立MISOCP模型&#xff0c;分时段优化&#xff0c;并行计算&#xff08;实时优化&#xff09;。 并对比了优化和未优化结果&#xff0c;验证了调度的有效性。 考虑到电动汽车的机动性&#xff0c;市区可分为三类功能区&#xff1a…

软考高项—信息系统项目管理师,2026全新汇总!

软考高项—信息系统项目管理师&#xff0c;2026全新汇总&#xff01; 链接&#xff1a;https://pan.quark.cn/s/30a4d39e1726

远程代打卡难根治?深度解析盖雅蓝牙打卡如何从底层杜绝考勤舞弊

在数字化办公日益普及的今天&#xff0c;考勤打卡作为企业管理的基础环节&#xff0c;正面临前所未有的信任挑战。市场上层出不穷的代打卡服务和各种模拟定位软件&#xff0c;让本应严肃的职场规则变成了一场技术博弈。这种被称为灰色产业链的现象&#xff0c;不仅让企业承担了…

20亿并购背后的AI Agent核心密码:LangChain与Manus联合揭秘上下文工程三大策略

2025年AI行业最重磅的新闻之一&#xff0c;莫过于Meta以20亿美金并购AI Agent领域的明星企业Manus。这笔天价交易让整个行业为之震动&#xff0c;也让Manus打造的颠覆性Agent成为焦点。为什么Manus的Agent能获得巨头如此青睐&#xff1f;它背后究竟隐藏着怎样的技术逻辑&#x…

网站meta标签添加Meta property=og协议的OG标签优化详解教程

1、网站meta标签添加Meta propertyog协议的OG标签优化详解教程<meta property"og:title" content"论文查重" />肯定有人见过这种的meta标签&#xff0c;那就会有人问这种是什么标签&#xff0c;干什么用的呢&#xff1f;2、Meta Propertyog是什么协议…

2026年最新爆火AI论文工具盘点:7款神器实测,知网维普查重一把过不留痕!

凌晨三点&#xff0c;论文查重率还在50%徘徊&#xff1f;导师的修改意见看得你头皮发麻&#xff0c;deadline只剩最后48小时&#xff1f; 别慌&#xff01;2026年最新爆火的AI论文工具已经帮你把“不可能”变成了“30分钟交稿”。这不再是科幻&#xff0c;而是无数研究生、本科…

当Ada记录类型含有可变长数组分量时的逆向分析

请看IDA生成的以下伪代码&#xff1a;char *a7;//函数的输入参数int v31; // ecxint v32; // ecxint v33; // edxint v34; // ebxint v35; // ecxint v36; // ecxint v37; // edxint v38; // eaxint v39; // edxv31 *a7;if ( v31 > 99 )v31 99;v32 (~(532 * v31 532) &g…

刀客doc:小米是时候切断和粉丝的脐带了

文/刀客doc(头条精选作者)刚刚&#xff0c;小米就“大熊事件”给出了一份相对完整的处理结果。公司发言人通过官微回应&#xff0c;公布了内部调查结论&#xff0c;文中称&#xff1a;“因为米粉&#xff0c;所以小米。米粉的信任和支持&#xff0c;是小米不断前行的基石。我们…

全网最全8个AI论文软件,专科生搞定毕业论文+格式规范!

全网最全8个AI论文软件&#xff0c;专科生搞定毕业论文格式规范&#xff01; AI 工具助力论文写作&#xff0c;专科生也能轻松应对 随着人工智能技术的不断发展&#xff0c;AI 工具在学术领域的应用越来越广泛。对于许多专科生来说&#xff0c;撰写毕业论文是一项既紧张又复杂的…

使用FOFA挖掘Grafana CVE-2025-4123漏洞与赏金获取全记录

我是如何通过FOFA发现Grafana中的“CVE-2025-4123”并获得赏金的 Hello Hunters&#xff01;我是Abdelrahman (A0xtrojan)&#xff0c;很高兴与大家分享我的技术文章&#xff0c;这篇文章记录了我在一个私人项目中的发现。话不多说&#xff0c;让我们直接进入正题。 发现漏洞 有…

USB TYPE-C 公头连接器设计规范总结:提升可靠性、降本增效的关键指南

在智能设备高速发展的时代&#xff0c;USB TYPE-C接口已成为行业标准连接方案&#xff0c;其公头连接器的设计质量直接影响产品性能、可靠性和成本效益。本规范文档&#xff08;基于VIVO等领先企业经验&#xff09;旨在为新产品开发及旧产品改善提供系统指导&#xff0c;避免常…

LSB与MSB:嵌入式开发中的核心概念详解

LSB与MSB&#xff1a;嵌入式开发中的核心概念详解 引言&#xff1a;二进制世界的方向标 在数字系统和嵌入式开发中&#xff0c;LSB&#xff08;Least Significant Bit&#xff09; 和 MSB&#xff08;Most Significant Bit&#xff09; 是理解数据表示和处理的基石概念。它们定…

手机电池连接器:现代电子设备的关键组件介绍

时&#xff0c;弹簧触点与电池端子接触&#xff0c;Housing引导对位&#xff0c;Solder Tab确保PCB端牢固。设计规范强调&#xff0c;这种模块化结构能缩短开发周期&#xff08;如通过预压T bar结构优化弹力&#xff09;&#xff0c;并避免常见失效&#xff0c;如接触不良或偏摆…

救命神器!10款AI论文写作软件测评:本科生毕业论文必备

救命神器&#xff01;10款AI论文写作软件测评&#xff1a;本科生毕业论文必备 2026年AI论文写作工具测评&#xff1a;为什么需要这份榜单&#xff1f; 随着人工智能技术的不断进步&#xff0c;越来越多的本科生开始借助AI工具提升论文写作效率。然而&#xff0c;面对市场上琳琅…

睡眠检测模型复现与调试完整解决方案

睡眠检测模型复现与调试完整解决方案 一、项目概述与背景 1.1 睡眠检测的重要性 睡眠质量对人类健康至关重要,睡眠障碍可导致多种健康问题。自动睡眠分期是睡眠医学中的重要任务,传统的人工标注费时费力且主观性强。 1.2 技术背景 深度学习在生物信号处理领域取得了显著…

导师推荐!8款一键生成论文工具测评,本科生写论文不再难

导师推荐&#xff01;8款一键生成论文工具测评&#xff0c;本科生写论文不再难 2026年学术写作工具测评&#xff1a;为何值得一看&#xff1f; 随着AI技术的不断进步&#xff0c;越来越多的本科生开始借助智能写作工具提升论文效率。然而&#xff0c;面对市场上琳琅满目的“一键…

【大学院-筆記試験練習:线性代数和数据结构(4)】

大学院-筆記試験練習&#xff1a;线性代数和数据结构&#xff08;&#xff14;&#xff09;1-前言2-线性代数-题目3-线性代数-参考答案4-数据结构-题目【問題1】ハッシュ法と衝突処理【問題2】連結リストの実装と操作【問題3】再帰処理と連結リスト【問題4】ソートアルゴリズム…

阶段三 在做movie这个项目时,有些同学使用本地高版本的JDK21来做这个项目时启动报错

一、错误现象 有些同学本地只有21版本的JDK&#xff0c;然后使用JDK21来做movie这个项目时&#xff0c;启动报错&#xff0c;如图 具体的错误信息&#xff1a; java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member fiel…