保存时间:2026/4/2 10:55:01
libv4l(Video for Linux 2)库。libv4l是 Linux 下用于视频设备访问的库,OpenCV 会借助它来与摄像头进行交互,从而获取摄像头的照片或视频。libv4l-dev等相关依赖,以提供对视频设备的支持。安装完成后,就可以使用 OpenCV 的相关函数来打开摄像头、读取视频帧等操作。libv4l-dev库(可以通过sudo apt-get install libv4l-dev命令安装)。#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#define WIDTH 640
#define HEIGHT 480
#define BUFFER_COUNT 4
struct buffer {
void *start;
size_t length;
};
int main(int argc, char **argv)
{
int fd;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct buffer *buffers;
int i;
// 打开摄像头设备文件
fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
if (fd < 0) {
perror("无法打开摄像头设备");
return -1;
}
// 设置视频格式为MJPEG
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)) {
perror("设置视频格式失败");
close(fd);
return -1;
}
// 请求分配缓冲区
memset(&req, 0, sizeof(req));
req.count = BUFFER_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(fd, VIDIOC_REQBUFS, &req)) {
perror("请求缓冲区失败");
close(fd);
return -1;
}
// 映射缓冲区
buffers = calloc(req.count, sizeof(*buffers));
for (i = 0; i < req.count; ++i) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) {
perror("查询缓冲区失败");
free(buffers);
close(fd);
return -1;
}
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.offset);
if (MAP_FAILED == buffers[i].start) {
perror("映射缓冲区失败");
free(buffers);
close(fd);
return -1;
}
}
// 将缓冲区放入队列
for (i = 0; i < req.count; ++i) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)) {
perror("将缓冲区放入队列失败");
for (i = 0; i < req.count; ++i)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
close(fd);
return -1;
}
}
// 开始捕获
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) {
perror("开始捕获失败");
for (i = 0; i < req.count; ++i)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
close(fd);
return -1;
}
// 从队列中取出一个缓冲区获取图像数据
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(fd, VIDIOC_DQBUF, &buf)) {
perror("取出缓冲区失败");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (i = 0; i < req.count; ++i)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
close(fd);
return -1;
}
// 将获取到的图像数据保存为文件(这里简单保存为jpeg格式)
FILE *fp = fopen("snapshot.jpg", "wb");
if (fp == NULL) {
perror("无法打开文件保存图像");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (i = 0; i < req.count; ++i)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
close(fd);
return -1;
}
fwrite(buffers[buf.index].start, 1, buf.bytesused, fp);
fclose(fp);
// 将缓冲区重新放入队列
if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)) {
perror("将缓冲区重新放入队列失败");
}
// 停止捕获
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type)) {
perror("停止捕获失败");
}
// 释放映射的缓冲区
for (i = 0; i < req.count; ++i)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
// 关闭摄像头设备
close(fd);
return 0;
}
"snapshot.jpg"文件。gcc编译该代码,例如:gcc v4l2_snapshot.c -o v4l2_snapshot -lv4l2,然后运行生成的可执行文件v4l2_snapshot来获取摄像头的快照。cv::Mat类来表示图像数据。可以使用cv::Mat的构造函数,根据已有的图像数据内存地址、图像宽度、高度、数据类型等信息来创建一个cv::Mat对象,从而让 OpenCV 能够对该图像进行处理。以下是一个简单示例:#include <opencv2/opencv.hpp>
int main() {
// 假设已经有了图像数据的内存地址、宽度、高度和数据类型
unsigned char* imageData = nullptr; // 这里替换为实际的图像数据内存地址
int width = 640;
int height = 480;
int channels = 3; // 例如RGB图像有3个通道
// 创建cv::Mat对象
cv::Mat image(height, width, CV_8UC3, imageData);
// 现在可以使用OpenCV的函数对image进行处理,例如显示图像
cv::imshow("Image", image);
cv::waitKey(0);
return 0;
}
imageData替换为从 V4L2 获取图像数据后的内存地址,并且根据实际情况设置正确的图像宽度、高度和数据类型。这样就可以将通过 V4L2 获取到的图像数据传递给 OpenCV 进行进一步的处理和分析。#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <opencv2/opencv.hpp>
#define VIDEO_DEVICE "/dev/video0"
int main() {
// 打开视频设备
int fd = open(VIDEO_DEVICE, O_RDWR);
if (fd < 0) {
std::cerr << "无法打开视频设备" << std::endl;
return -1;
}
// 设置视频格式为YUV420P
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420P;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
std::cerr << "无法设置视频格式" << std::endl;
close(fd);
return -1;
}
// 分配缓冲区
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
std::cerr << "无法分配缓冲区" << std::endl;
close(fd);
return -1;
}
// 将缓冲区映射到用户空间
unsigned char* buffer[4];
size_t buffer_size[4];
for (int i = 0; i < 4; ++i) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
std::cerr << "无法查询缓冲区" << std::endl;
close(fd);
return -1;
}
buffer_size[i] = buf.length;
buffer[i] = (unsigned char*)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (buffer[i] == MAP_FAILED) {
std::cerr << "无法映射缓冲区" << std::endl;
close(fd);
return -1;
}
}
// 将缓冲区放入队列
for (int i = 0; i < 4; ++i) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
std::cerr << "无法将缓冲区放入队列" << std::endl;
close(fd);
return -1;
}
}
// 开始捕获
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
std::cerr << "无法开始捕获" << std::endl;
close(fd);
return -1;
}
// 从队列中取出缓冲区,获取图像数据
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
std::cerr << "无法取出缓冲区" << std::endl;
close(fd);
return -1;
}
// 将YUV数据转换为RGB
cv::Mat yuvImage(480 + 480 / 2, 640, CV_8UC1, buffer[buf.index]);
cv::Mat rgbImage;
cv::cvtColor(yuvImage, rgbImage, cv::COLOR_YUV420P2RGB);
// 显示图像
cv::imshow("RGB Image", rgbImage);
cv::waitKey(0);
// 停止捕获
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
std::cerr << "无法停止捕获" << std::endl;
close(fd);
return -1;
}
// 释放缓冲区
for (int i = 0; i < 4; ++i) {
munmap(buffer[i], buffer_size[i]);
}
// 关闭视频设备
close(fd);
return 0;
}
cvtColor函数将 YUV 数据转换为 RGB 格式,并显示图像。最后,停止捕获,释放缓冲区并关闭视频设备。VIDEO_DEVICE宏定义了要打开的视频设备节点,你可能需要根据实际情况进行修改。同时,确保编译时链接了 OpenCV 库和必要的系统库。Mat对象时,需要根据具体的 YUV 格式进行正确的设置和处理。例如,对于 YUV420P 格式,可以像前面代码中那样,根据图像的宽、高和通道数来创建Mat对象,将 YUV 数据正确地映射到Mat中。#include <opencv2/opencv.hpp>
#include <iostream>
// 回调函数,当轨迹条的值改变时被调用
void onTrackbarChange(int pos, void* userdata) {
cv::Mat* img = static_cast<cv::Mat*>(userdata);
std::cout << "按钮被点击,当前值: " << pos << std::endl;
// 这里可以添加你想要执行的操作,比如处理图像等
// 例如,简单地在图像上绘制一些东西
cv::putText(*img, "Button Clicked", cv::Point(100, 100), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
cv::imshow("UI Example", *img);
}
int main() {
// 创建一个空白图像用于显示界面
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
// 创建窗口
cv::namedWindow("UI Example");
// 创建轨迹条(模拟按钮)
int buttonValue = 0;
cv::createTrackbar("Button", "UI Example", &buttonValue, 1, onTrackbarChange, &img);
// 显示图像
cv::imshow("UI Example", img);
// 等待按键输入,这里使用'q'键退出程序
while (cv::waitKey(1) != 'q') {}
cv::destroyAllWindows();
return 0;
}
img,用于显示用户界面。cv::namedWindow创建一个名为 “UI Example” 的窗口。cv::createTrackbar创建一个轨迹条,将其命名为 “Button”,初始值为 0,最大值为 1。当轨迹条的值改变时,会调用onTrackbarChange回调函数。onTrackbarChange函数中,简单地打印了一条消息,并在图像上绘制了一些文本,然后更新显示图像。cv::waitKey等待用户按键输入,当按下'q'键时退出程序,并使用cv::destroyAllWindows关闭所有窗口。N 键时,会在控制台输出相应的提示信息:#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个空白图像用于显示窗口
cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);
// 创建窗口
cv::namedWindow("Key Event Example");
// 显示图像(窗口必须有内容显示才能正常捕捉事件)
cv::imshow("Key Event Example", img);
while (true) {
// 等待按键事件,参数1表示等待时间(单位:毫秒),这里设为1毫秒
int key = cv::waitKey(1);
// 判断按下的键是否为字母 'N'(注意区分大小写,这里是大写 'N')
if (key == 'N') {
std::cout << "You pressed the 'N' key!" << std::endl;
// 在这里可以添加按下 'N' 键后要执行的其他操作,比如处理图像等
}
// 判断按下的键是否为 'q',用于退出循环和关闭窗口
else if (key == 'q') {
break;
}
}
// 销毁所有窗口
cv::destroyAllWindows();
return 0;
}
img,并使用 cv::namedWindow 创建了一个名为 "Key Event Example" 的窗口,然后通过 cv::imshow 将图像显示在窗口中。while 循环,通过 cv::waitKey(1) 不断检测是否有按键事件发生,cv::waitKey 的返回值为按下键的 ASCII 码。N,如果是则在控制台输出提示信息,同时你可以在相应的 if 语句块中添加更多自定义的操作。q 时,跳出循环并使用 cv::destroyAllWindows 关闭所有窗口,结束程序。