鱼眼摄像头(一)多平面格式 单缓冲读取图像并显示
鱼眼摄像头(一)多平面格式 单缓冲读取图像并显示
1.摄像头格式
1. 单平面格式(Single Plane):各通道数据保存在同一个平面(缓冲),图像数据按行连续存储a. mjpeg,yuyv等,适用于轻量级或者简单场景
2. 多平面格式(Multi-plane):图像数据分别保存在多个平面(缓冲)a. NV12,Y平面+UV平面b. 每个平面都有自己单独的物理内存缓冲区。因此对于ISP,写入DMA等场景来说分通道处理更高效c. 代码层面对于多平面数据的采集可以借助v4l2
2.流程图
3.重要流程详解
- 设置格式
没搞懂为什么nv12反而还是设置一个plane,不是多平面么
bool V4L2MPlaneCamera::initFormat()
{struct v4l2_format fmt = {};fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;fmt.fmt.pix_mp.width = width_;fmt.fmt.pix_mp.height = height_;fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;fmt.fmt.pix_mp.num_planes = 1;return ioctl(fd_, VIDIOC_S_FMT, &fmt) >= 0;
}
- 请求缓冲区,获取申请的缓冲区信息,用于后续取数据
i. 测试可申请一个,但是读写可能会冲突,程序得等待写完再读,读的时候无法写,效率不高
ii. 实际工作根据场景申请3-6个,流水线采集,更稳定,也可根据需要申请更多6-12
bool V4L2MPlaneCamera::initMMap()
{if (!initFormat())return false;struct v4l2_requestbuffers req = {};req.count = 1;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd_, VIDIOC_REQBUFS, &req) < 0)return false;struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.m.planes = planes;buf.length = 1; // 表示 planes 数组中有多少个 plane// 向驱动请求第 index 个缓冲区的详细信息,比如大小、偏移位置,实际 plane 的个数等。if (ioctl(fd_, VIDIOC_QUERYBUF, &buf) < 0)return false;buffer_.length = buf.m.planes[0].length; // plane 的实际内存长度buffer_.start = mmap(NULL, buf.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, buf.m.planes[0].m.mem_offset);return buffer_.start != MAP_FAILED;
}
- 启动采集,注意每次采集前必须设置缓冲区队列
ioctl(fd_, VIDIOC_QBUF, &buf)
bool V4L2MPlaneCamera::queueBuffer()
{struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.length = 1; // 本缓冲区包含多少个 plane(平面)。buf.m.planes = planes; // 接收 plane 结果planes[0].length = buffer_.length; // plane 的实际内存长度return ioctl(fd_, VIDIOC_QBUF, &buf) >= 0;
}bool V4L2MPlaneCamera::startCapture()
{if (!queueBuffer())return false;enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;return ioctl(fd_, VIDIOC_STREAMON, &type) >= 0;
}
- 取帧数据,再送入队列
根据前面获取到的缓冲区数据及mmap信息,直接取出缓冲区数据,转换为opencv格式并显示,最后再将取出的缓冲区放回队列,若不放回,驱动不会再写入数据
bool V4L2MPlaneCamera::readFrame(cv::Mat &bgr)
{fd_set fds;FD_ZERO(&fds);FD_SET(fd_, &fds);// 等待1s后返回struct timeval tv = {1, 0};int r = select(fd_ + 1, &fds, NULL, NULL, &tv);if (r <= 0)return false;struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.length = 1; // 本缓冲区包含多少个 plane(平面)。buf.m.planes = planes; // 接收 plane 结果// 从缓冲队列中取出一帧缓冲区if (ioctl(fd_, VIDIOC_DQBUF, &buf) < 0)return false;cv::Mat yuv(height_ + height_ / 2, width_, CV_8UC1, buffer_.start);cv::cvtColor(yuv, bgr, cv::COLOR_YUV2BGR_NV12);// 将取出的缓冲区重新放回队列if (!queueBuffer())return false;return true;
}
3.完整代码
#ifndef V4L2MPLANECAMERA_H
#define V4L2MPLANECAMERA_H#pragma once#include <linux/videodev2.h>#include <opencv2/opencv.hpp>
#include <string>
#include <vector>struct FormatInfo {std::string fourcc;std::string description;std::vector<std::pair<int, int>> resolutions;
};class V4L2MPlaneCamera {public:V4L2MPlaneCamera(const std::string &devPath, int width, int height);~V4L2MPlaneCamera();std::vector<FormatInfo> listFormats();bool openDevice();bool initMMap();bool startCapture();bool stopCapture();void closeDevice();bool readFrame(cv::Mat &bgr);private:struct Buffer {void *start;size_t length;};std::string devicePath_;int width_, height_;int fd_ = -1;Buffer buffer_;int bufferCount_ = 4;enum v4l2_buf_type bufferType_ = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;bool initFormat();bool queueBuffer();
};#endif
#include "V4L2MPlaneCamera.h"#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>#include <cstring>
#include <iostream>static std::string fourccToString(__u32 fmt);V4L2MPlaneCamera::V4L2MPlaneCamera(const std::string &devPath, int width,int height): devicePath_(devPath), width_(width), height_(height) {}V4L2MPlaneCamera::~V4L2MPlaneCamera() {stopCapture();closeDevice();
}bool V4L2MPlaneCamera::openDevice() {fd_ = ::open(devicePath_.c_str(), O_RDWR | O_NONBLOCK);return fd_ >= 0;
}bool V4L2MPlaneCamera::initFormat() {struct v4l2_format fmt = {};fmt.type = bufferType_;fmt.fmt.pix_mp.width = width_;fmt.fmt.pix_mp.height = height_;fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;fmt.fmt.pix_mp.num_planes = 1; // NV12只有一个planereturn ioctl(fd_, VIDIOC_S_FMT, &fmt) >= 0;
}bool V4L2MPlaneCamera::initMMap() {if (!initFormat()) return false;struct v4l2_requestbuffers req = {};req.count = 1;req.type = bufferType_;req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd_, VIDIOC_REQBUFS, &req) < 0) return false;struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = bufferType_;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.m.planes = planes;buf.length = 1; // 表示 planes 数组中有多少个 plane// 向驱动请求第 index 个缓冲区的详细信息,比如大小、偏移位置,实际 plane// 的个数等。if (ioctl(fd_, VIDIOC_QUERYBUF, &buf) < 0) return false;buffer_.length = buf.m.planes[0].length; // plane 的实际内存长度buffer_.start = mmap(NULL, buf.m.planes[0].length, PROT_READ | PROT_WRITE,MAP_SHARED, fd_, buf.m.planes[0].m.mem_offset);return buffer_.start != MAP_FAILED;
}bool V4L2MPlaneCamera::queueBuffer() {struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = bufferType_;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.length = 1; // 本缓冲区包含多少个 plane(平面)。buf.m.planes = planes; // 接收 plane 结果planes[0].length = buffer_.length; // plane 的实际内存长度return ioctl(fd_, VIDIOC_QBUF, &buf) >= 0;
}bool V4L2MPlaneCamera::startCapture() {if (!queueBuffer()) return false;return ioctl(fd_, VIDIOC_STREAMON, &bufferType_) >= 0;
}bool V4L2MPlaneCamera::readFrame(cv::Mat &bgr) {fd_set fds;FD_ZERO(&fds);FD_SET(fd_, &fds);// 等待1s后返回struct timeval tv = {1, 0};int r = select(fd_ + 1, &fds, NULL, NULL, &tv);if (r <= 0) return false;struct v4l2_buffer buf = {};struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};buf.type = bufferType_;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0; // 缓冲区索引buf.length = 1; // 本缓冲区包含多少个 plane(平面)。buf.m.planes = planes; // 接收 plane 结果// 从缓冲队列中取出一帧缓冲区if (ioctl(fd_, VIDIOC_DQBUF, &buf) < 0) return false;cv::Mat yuv(height_ + height_ / 2, width_, CV_8UC1, buffer_.start);cv::cvtColor(yuv, bgr, cv::COLOR_YUV2BGR_NV12);// 将取出的缓冲区重新放回队列if (!queueBuffer()) return false;return true;
}bool V4L2MPlaneCamera::stopCapture() {return ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_) >= 0;
}void V4L2MPlaneCamera::closeDevice() {if (fd_ >= 0) {munmap(buffer_.start, buffer_.length);close(fd_);fd_ = -1;}
}std::vector<FormatInfo> V4L2MPlaneCamera::listFormats() {std::vector<FormatInfo> formats;if (fd_ < 0) {std::cerr << "[错误] 摄像头设备未打开。" << std::endl;return formats;}struct v4l2_fmtdesc fmtDesc = {};fmtDesc.type = bufferType_;std::cout << "[信息] 开始枚举支持的图像格式..." << std::endl;for (fmtDesc.index = 0; ioctl(fd_, VIDIOC_ENUM_FMT, &fmtDesc) == 0;fmtDesc.index++) {FormatInfo info;info.fourcc = fourccToString(fmtDesc.pixelformat);info.description = reinterpret_cast<char *>(fmtDesc.description);std::cout << " - 格式: " << info.fourcc << " (" << info.description << ")"<< std::endl;struct v4l2_frmsizeenum sizeEnum = {};sizeEnum.pixel_format = fmtDesc.pixelformat;bool hasResolution = false;for (sizeEnum.index = 0; ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &sizeEnum) == 0;sizeEnum.index++) {switch (sizeEnum.type) {case V4L2_FRMSIZE_TYPE_DISCRETE:std::cout << " 离散分辨率: " << sizeEnum.discrete.width << "x"<< sizeEnum.discrete.height << std::endl;info.resolutions.emplace_back(sizeEnum.discrete.width,sizeEnum.discrete.height);hasResolution = true;break;case V4L2_FRMSIZE_TYPE_CONTINUOUS:std::cout << " 连续分辨率范围: " << sizeEnum.stepwise.min_width<< "x" << sizeEnum.stepwise.min_height << " 到 "<< sizeEnum.stepwise.max_width << "x"<< sizeEnum.stepwise.max_height << "(任意值均可)"<< std::endl;hasResolution = true;// 可考虑生成若干预设分辨率加入 info.resolutionsbreak;case V4L2_FRMSIZE_TYPE_STEPWISE:std::cout << " 步进型分辨率: "<< "宽度 [" << sizeEnum.stepwise.min_width << "~"<< sizeEnum.stepwise.max_width << "] 步长 "<< sizeEnum.stepwise.step_width << ",高度 ["<< sizeEnum.stepwise.min_height << "~"<< sizeEnum.stepwise.max_height << "] 步长 "<< sizeEnum.stepwise.step_height << std::endl;hasResolution = true;// 同上可生成预设 resolutionbreak;default:std::cerr << " [警告] 未知分辨率类型: " << sizeEnum.type<< std::endl;break;}}if (!hasResolution) {std::cerr << " [警告] 无可用分辨率" << std::endl;}formats.push_back(info);}if (fmtDesc.index == 0) {std::cerr << "[警告] 未能枚举到任何图像格式!" << std::endl;} else {std::cout << "[信息] 完成格式枚举,总共: " << fmtDesc.index << " 种格式"<< std::endl;}return formats;
}
static std::string fourccToString(__u32 fmt) {char str[5];str[0] = fmt & 0xFF;str[1] = (fmt >> 8) & 0xFF;str[2] = (fmt >> 16) & 0xFF;str[3] = (fmt >> 24) & 0xFF;str[4] = '\0';return std::string(str);
}
相关文章:

鱼眼摄像头(一)多平面格式 单缓冲读取图像并显示
鱼眼摄像头(一)多平面格式 单缓冲读取图像并显示 1.摄像头格式 1. 单平面格式(Single Plane):各通道数据保存在同一个平面(缓冲),图像数据按行连续存储a. mjpeg,yuyv等…...

机器学习笔记——特征工程
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本笔记介绍机器学习中常见的特征工程方法、正则化方法和简要介绍强化学习。 文章目录 特征工程(Fzeature Engineering)1. 特征提取ÿ…...

A Survey of Learning from Rewards:从训练到应用的全面剖析
A Survey of Learning from Rewards:从训练到应用的全面剖析 你知道大语言模型(LLMs)如何通过奖励学习变得更智能吗?这篇论文将带你深入探索。从克服预训练局限的新范式,到训练、推理各阶段的策略,再到广泛…...

Python爬虫第20节-使用 Selenium 爬取小米商城空调商品
目录 前言 一、 本文目标 二、环境准备 2.1 安装依赖 2.2 配置 ChromeDriver 三、小米商城页面结构分析 3.1 商品列表结构 3.2 分页结构 四、Selenium 自动化爬虫实现 4.1 脚本整体结构 4.2 代码实现 五、关键技术详解 5.1 Selenium 启动与配置 5.2 页面等待与异…...
无线定位之 三 SX1302 网关源码 thread_gps 线程详解
前言 笔者计划通过无线定位系列文章、系统的描述 TDOA 无线定位和混合定位相关技术知识点, 并以实践来验证此定位系统精度。 笔者从实践出发、本篇直接走读无线定位系统关键节点、网关 SX1302 源码框架,并在源码走读过程 中、着重分析与无线定位相关的PPS时间的来龙去脉、并在…...

Aware和InitializingBean接口以及@Autowired注解失效分析
Aware 接口用于注入一些与容器相关信息,例如: a. BeanNameAware 注入 Bean 的名字 b. BeanFactoryAware 注入 BeanFactory 容器 c. ApplicationContextAware 注入 ApplicationContext 容器 d. EmbeddedValueResolverAware 注入 解析器&a…...

Unity3D仿星露谷物语开发41之创建池管理器
1、目标 在PersistentScene中创建池管理器(Pool Manager)。这将允许一个预制对象池被创建和重用。 在游戏中当鼠标点击地面时,便会启用某一个对象。比如点击地面,就创建了一棵树,而这棵树是从预制体对象池中获取的&a…...

Modbus协议介绍
Modbus是一种串行通信协议,由Modicon公司(现为施耐德电气)在1979年为可编程逻辑控制器(PLC)通信而开发。它是工业自动化领域最常用的通信协议之一,具有开放性、简单性和跨平台兼容性,广泛应用于…...
深度学习遇到的问题处理
小土堆课程学习 1.tensorboard远程到本地无法显示 1.检查本地与远程端口是否被占用 2.一定要在远程服务器的项目下创建对应的存储文件夹 且 远程服务器一定要有需要处理的数据 ## 此时远程项目路径下有logs文件夹 存放上传的图像与数据 writerSummaryWriter("logs"…...

I/O多路复用(select/poll/epoll)
通过一个进程来维护多个Socket,也就是I/O多路复用,是一种常见的并发编程技术,它允许单个线程或进程同时监视多个输入/输出(I/O)流(例如网络连接、文件描述符)。当任何一个I/O流准备好进行读写操…...

Westlake-Omni 情感端音频生成式输出模型
简述 github地址在 GitHub - xinchen-ai/Westlake-OmniContribute to xinchen-ai/Westlake-Omni development by creating an account on GitHub.https://github.com/xinchen-ai/Westlake-Omni Westlake-Omni 是由西湖心辰(xinchen-ai)开发的一个开源…...
Egg.js知识框架
一、Egg.js 核心概念 1. Egg.js 简介 基于 Koa 的企业级 Node.js 框架(阿里开源) 约定优于配置(Convention over Configuration) 插件化架构,内置多进程管理、日志、安全等能力 适合中大型企业应用,提供…...

随手记录5
一些顶级思维: 顶级思维 1、永远不要自卑。 也永远不要感觉自己比别人差,这个人有没有钱,有多少钱,其实跟你都没有关系。有很多人就是那个奴性太强,看到比自己优秀的人,甚至一些装逼的人,这…...

Linux驱动:驱动编译流程了解
要求 1、开发板中的linux的zImage必须是自己编译的 2、内核源码树,其实就是一个经过了配置编译之后的内核源码。 3、nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器。 内核源码树 解压 tar -jxvf x210kernel.tar.bz2 编译 make x210ii_qt_defconfigmakeCan’t use ‘…...

使用 Flowise 构建基于私有知识库的智能客服 Agent(图文教程)
使用 Flowise 构建基于私有知识库的智能客服 Agent(图文教程) 在构建 AI 客服时,常见的需求是让机器人基于企业自身的知识文档,提供准确可靠的答案。本文将手把手教你如何使用 Flowise + 向量数据库(如 Pinecone),构建一个结合 RAG(Retrieval-Augmented Generation)检…...

RabbitMQ ③-Spring使用RabbitMQ
Spring使用RabbitMQ 创建 Spring 项目后,引入依赖: <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp --> <dependency><groupId>org.springframework.boot</groupId><artifac…...
测试文章标题01
模型上下文协议(Model Context Protocol, MCP)深度解析 一、MCP的核心概念 模型上下文协议(Model Context Protocol, MCP)是一种用于规范机器学习模型与外部环境交互的标准化框架。其核心目标是通过定义统一的接口和数据格式&am…...

linux中常用的命令(四)
目录 1-cat查看文件内容 2-more命令 3-less命令 4-head命令 5-tail命令 1-cat查看文件内容 cat中的一些操作 -b : 列出行号(不含空白行)-E : 将结尾的断行以 $ 的形式展示出来-n : 列出行号(含空白行)-T : 将 tab 键 以 ^I 显示…...
2025年阿里云大数据ACP高级工程师认证模拟试题(附答案解析)
这篇文章的内容是阿里云大数据ACP高级工程师认证考试的模拟试题。 所有模拟试题由AI自动生成,主要为了练习和巩固知识,并非所谓的 “题库”,考试中如果出现同样试题那真是纯属巧合。 1、下列关于MaxCompute的描述中,错误的是&am…...
【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit
1.问题描述: 预览PDF文件,文档上所描述的loadDocument接口,可以返回文件的状态,并无法实现PDF的预览,是否有能预览PDF相关接口? 解决方案: 1、执行loadDocument进行加载PDF文件后,…...
二元随机响应(Binary Randomized Response, RR)的翻转概率
随机响应(Randomized Response)机制 ✅ 回答核心: p 1 1 e ε 才是「翻转概率」 \boxed{p \frac{1}{1 e^{\varepsilon}}} \quad \text{才是「翻转概率」} p1eε1才是「翻转概率」 而: q e ε 1 e ε 是「保留真实值」…...
hive两个表不同数据类型字段关联引发的数据倾斜
不同数据类型引发的Hive数据倾斜解决方案 #### 一、原因分析 当两个表的关联字段存在数据类型不一致时(如int vs string、bigint vs decimal),Hive会触发隐式类型转换引发以下问题: Key值的精度损失:若关联字…...

利用SSRF击穿内网!kali靶机实验
目录 1. 靶场拓扑图 2. 判断SSRF的存在 3. SSRF获取本地信息 3.1. SSRF常用协议 3.2. 使用file协议 4. 172.150.23.1/24探测端口 5. 172.150.23.22 - 代码注入 6. 172.150.23.23 SQL注入 7. 172.150.23.24 命令执行 7.1. 实验步骤 8. 172.150.23.27:6379 Redis未授权…...

DVWA在线靶场-xss部分
目录 1. xxs(dom) 1.1 low 1.2 medium 1.3 high 1.4 impossible 2. xss(reflected) 反射型 2.1 low 2.2 medium 2.3 high 2.4 impossible 3. xss(stored)存储型 --留言板 3.1 low 3.2 medium 3.3 high 3.…...

Go 语言 slice(切片) 的使用
序言 在许多开发语言中,动态数组是必不可少的一个组成部分。在实际的开发中很少会使用到数组,因为对于数组的大小大多数情况下我们是不能事先就确定好的,所以他不够灵活。动态数组通过提供自动扩容的机制,极大地提升了开发效率。这…...
Android Exoplayer 实现多个音视频文件混合播放以及音轨切换
在之前的文章ExoPlayer中常见MediaSource子类的区别和使用场景中介绍了Exoplayer中各种子MediaSource的使用场景,这篇我们着重详细介绍下实现多路流混合播放的用法。常见的使用场景有:视频文件电影字幕、正片视频广告视频、背景视频背景音乐等。 初始化…...
深入浅出:Java 中的动态类加载与编译技术
1. 引言 Java 的动态性是其强大功能之一,允许开发者在运行时加载和编译类,从而构建灵活、可扩展的应用程序。动态类加载和编译在许多高级场景中至关重要,例如插件系统、动态代理、框架开发(如 Spring)和代码生成工具。Java 提供了两大核心机制来实现这一目标: 自定义 Cl…...

js常用的数组遍历方式
以下是一个完整的示例,将包含图片、文字和数字的数组渲染到 HTML 页面,使用 多种遍历方式 实现不同的渲染效果: 1. 准备数据(数组) const items [{ id: 1, name: "苹果", price: 5.99, image: "h…...

【网络编程】五、三次握手 四次挥手
文章目录 Ⅰ. 三次握手Ⅱ. 建立连接后的通信Ⅲ. 四次挥手 Ⅰ. 三次握手 1、首先双方都是处于未通信的状态,也就是关闭状态 CLOSE。 2、因为服务端是为了服务客户端的,所以它会提前调用 listen() 函数进行对客户端请求的监听。 3、接着客户端就…...
【类拷贝文件的运用】
常用示例 当我们面临将文本文件分成最大大小块的时,我们可能会尝试编写如下代码: public class TestSplit {private static final long maxFileSizeBytes 10 * 1024 * 1024; // 默认10MBpublic void split(Path inputFile, Path outputDir) throws IOException {…...