目录
QT 中使用摄像头采集
QT 中使用摄像头就比较简单了,已经封装好了几个类直接使用即可,下面根据获取摄像头信息,创建摄像头对象等一步一步来介绍每个类。
注意: 目前只能在 window 使用,无法交叉编译到 linux 系统的开发板上
交叉编译无法识别这两个类库。
首先 在项目文件中加入多媒体模块,多媒体模块中包含了处理摄像头的类
QT += multimedia multimediawidgets
1> 摄像头信息类 (QCameraInfo)
QCameraInfo
类用于查询可用的摄像头设备信息。你可以使用它来获取有关系统上连接的所有摄像头的详细信息(例如,设备名称、分辨率等),然后选择一个摄像头进行使用。
函数 | 说明 |
---|---|
QCameraInfo() | 默认构造函数,不会返回具体信息 |
QCameraInfo(const QString &deviceName) | 通过设备名称创建摄像头信息对象 |
static QList<QCameraInfo> availableCameras() | 返回可用摄像头的列表 |
QString deviceName() const | 获取设备名称 |
QString description() const | 获取摄像头描述信息 |
QCameraInfo::Position position() const | 获取摄像头位置(前置/后置) |
bool isNull() const | 判断 QCameraInfo 是否为空 |
示例
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
for (const QCameraInfo &cameraInfo : cameras) {
qDebug() << "Device Name: " << cameraInfo.deviceName();
qDebug() << "Description: " << cameraInfo.description();
}
2> 摄像头类 (QCamera)
QCamera
类是 Qt 提供的用于访问和控制摄像头的类,它封装了对摄像头硬件的控制。
函数 | 说明 |
---|---|
QCamera(QObject *parent = nullptr) | 使用默认摄像头创建 QCamera |
QCamera(const QCameraInfo &cameraInfo, QObject *parent = nullptr) | 指定摄像头设备创建 QCamera |
void start() | 启动摄像头 |
void stop() | 停止摄像头 |
void setViewfinder(QAbstractVideoSurface *surface) | 设置摄像头的视频输出目标 |
void setCaptureMode(QCamera::CaptureMode mode) | 设置捕获模式(视频或静态图像) |
QCameraImageCapture *imageCapture() const | 获取 QCameraImageCapture 用于图像捕获 |
void setFocusMode(QCameraFocus::FocusMode mode) | 设置摄像头对焦模式 |
void setExposureMode(QCameraExposure::ExposureMode mode) | 设置曝光模式 |
示例
// 选择第一个可用摄像头并启动
QCameraInfo cameraInfo = QCameraInfo::availableCameras().first();
QCamera *camera = new QCamera(cameraInfo, this);
QCameraViewfinder *viewfinder = new QCameraViewfinder(this);
camera->setViewfinder(viewfinder);//绑定摄像头到显示视图上
camera->start();
3> 摄像头截屏类(QCameraImageCapture)
QCameraImageCapture
类用于捕获静态图像,并将其保存或处理。它通常与 QCamera
配合使用。
函数 | 说明 |
---|---|
QCameraImageCapture(QCamera *camera, QObject *parent = nullptr) | 创建用于捕获图像的对象 |
void capture(int id = -1) | 捕获图像 |
void setCaptureDestination(QCameraImageCapture::CaptureDestination destination) | 设置图像捕获的目标位置(内存、文件等) |
signals: void imageCaptured(int id, const QImage &preview) | 捕获图像后触发的信号 |
QCameraImageCapture::capture(const QString &file = QString()) | 传入要保存文件的路径 |
- 注意:摄像头捕获图像会触发
imageCaptured
信号,你可以在这个信号槽函数对图像进一步处理。
QCameraImageCapture *imageCapture = new QCameraImageCapture(camera, this);
connect(imageCapture, &QCameraImageCapture::imageCaptured,
this, &MainWindow::onImageCaptured);
// 捕获图像
imageCapture->capture();
4> 显示视图类(QCameraViewfinder)
QCameraViewfinder
是用于显示摄像头画面的视图窗口。你将摄像头的输出设置为这个视图窗口,就可以显示摄像头的画面。
函数 | 说明 |
---|---|
QCameraViewfinder(QWidget *parent = nullptr) | 创建视图窗口用于显示摄像头画面 |
void show() | 显示视图窗口 |
void resize(int width, int height) | 调整视图窗口大小 |
void setRenderHints(QPainter::RenderHints hints) | 设置渲染选项(如抗锯齿) |
示例:
QCameraViewfinder *viewfinder = new QCameraViewfinder(this);
viewfinder->setGeometry(0, 0, 640, 480); // 设置视图窗口大小
viewfinder->show();
5> 综合示例
QT 中融入 C 语言摄像头代码
思路:
- 模仿当初我们硬件控制led灯的代码
- 把你写的C语言代码封装成C++类添加到你的QT工程中
具体实现方法:
方法一:用定时器实现
方法二:用多线程实现
注意:开发板中使用多线程(死循环的),如果无法终止(windows上可以的),解决方法用标志位来控制线程退出
方法一实现:
新建一个 cpp 文件
#ifndef MYCAMERA_H
#define MYCAMERA_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <linux/videodev2.h> //V4L2的头文件,写代码需要用到这个头文件定义的一些数据类型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define W 640
#define H 480
//自定义结构体把每个缓冲块的首地址和大小存放起来
struct bufmsg
{
void *start; //存放每个缓冲块的首地址
int len; //存放每个缓冲块的实际大小
};
class mycamera
{
public:
mycamera();
int camera_init(); //初始化摄像头
int camera_captrue(); //摄像头出队入队显示画面
int camera_uninit(); //关闭摄像头
private:
int camerafd;
int lcdfd;
int *lcdmem;
//定义结构体数组存放四个缓冲块的信息
struct bufmsg array[4];
enum v4l2_buf_type mytype;
struct v4l2_buffer vbuf;
};
#endif // MYCAMERA_H
#include "mycamera.h"
//封装函数把一组YUV转换得到ARGB数据
int yuvtoargb(int y, int u, int v) {
int r, g, b;
int pix;
// 使用转换公式
r = y + 1.4075 * (v - 128);
g = y - 0.3455 * (u - 128) - 0.7169 * (v - 128);
b = y + 1.779 * (u - 128);
// 修正溢出
if (r > 255) r = 255; if (r < 0) r = 0;
if (g > 255) g = 255; if (g < 0) g = 0;
if (b > 255) b = 255; if (b < 0) b = 0;
// 组装成 ARGB 格式(Alpha 通道设为 0x00)
pix = (0x00 << 24) | (r << 16) | (g << 8) | b;
return pix;
}
int allyuyvtoargb(char *yuyvdata,int *argbdata)
{
int i,j;
for(i=0,j=0; i<W*H; i+=2,j+=4) //循环总次数 W*H/2
{
//一组YUYV可以计算得到两组ARGB
//Y1跟UV配合
argbdata[i]=yuvtoargb(yuyvdata[j],yuyvdata[j+1],yuyvdata[j+3]);
//Y2跟UV配合
argbdata[i+1]=yuvtoargb(yuyvdata[j+2],yuyvdata[j+1],yuyvdata[j+3]);
}
return 0;
}
//初始化摄像头
int mycamera::camera_init()
{
int i;
int ret;
//打开液晶屏的驱动
lcdfd=open("/dev/fb0",O_RDWR);
if(lcdfd==-1)
{
perror("打开液晶屏失败了!\n");
return -1;
}
//映射得到液晶屏的首地址
lcdmem=(int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
if(lcdmem==NULL)
{
perror("映射液晶屏失败了!\n");
return -1;
}
//打开摄像头的驱动
camerafd=open("/dev/video7",O_RDWR);
if(camerafd==-1)
{
perror("打开摄像头驱动失败了!\n");
return -1;
}
//提前设置好摄像头的采集格式
struct v4l2_format vfmt;
bzero(&vfmt,sizeof(vfmt));
vfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width=W; //摄像头采集画面的宽
vfmt.fmt.pix.height=H; //摄像头采集画面的高
vfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //摄像头采集画面的格式
/*
V4L2_PIX_FMT_YUYV 表示YUV格式的画面
V4L2_PIX_FMT_JPEG 表示jpg格式的画面
*/
ret=ioctl(camerafd,VIDIOC_S_FMT,&vfmt);
if(ret==-1)
{
perror("设置摄像头采集格式失败了!\n");
return -1;
}
//给摄像头的驱动发送命令申请缓冲区
struct v4l2_requestbuffers reqbuf;
bzero(&reqbuf,sizeof(reqbuf));
reqbuf.count=4; //申请4个缓冲块
reqbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_REQBUFS,&reqbuf);
if(ret==-1)
{
perror("申请缓冲区失败了!\n");
return -1;
}
//分配缓冲区给你
for(i=0; i<4; i++) //第三步你申请了4个缓冲,因此需要循环四次分配四个不同的缓冲区给你
{
bzero(&vbuf,sizeof(vbuf));
vbuf.index=i; //缓冲块的索引号
vbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
vbuf.memory=V4L2_MEMORY_MMAP;
ret=ioctl(camerafd,VIDIOC_QUERYBUF,&vbuf);
if(ret==-1)
{
perror("分配缓冲区失败了!\n");
return -1;
}
//顺便映射得到每个缓冲区的首地址--》方便后续我们取出画面操作(通过指针运算搞定)
array[i].len=vbuf.length; //存放大小
array[i].start=mmap(NULL,vbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,vbuf.m.offset);
if(array[i].start==NULL)
{
perror("映射缓冲块的首地址失败了!\n");
return -1;
}
//顺便申请画面入队--》跟摄像头说我打算等一会把画面数据往四个内存块里面存放了(先打招呼)
ret=ioctl(camerafd,VIDIOC_QBUF,&vbuf);
if(ret==-1)
{
perror("申请入队失败了!\n");
return -1;
}
}
//开始启动摄像头采集画面--》画面马上就有了
mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
if(ret==-1)
{
perror("启动摄像头开始采集画面失败了!\n");
return -1;
}
return 0;
}
//出队,入队显示画面
int mycamera::camera_captrue()
{
int i,j;
int ret;
//定义数组存放转换得到一帧ARGB数据
int argbbuf[W*H];
for(i=0; i<4; i++)
{
bzero(&vbuf,sizeof(vbuf));
vbuf.index=i; //缓冲块的索引号
vbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
vbuf.memory=V4L2_MEMORY_MMAP;
//出队
ret=ioctl(camerafd,VIDIOC_DQBUF,&vbuf);
if(ret==-1)
{
perror("申请出队失败了!\n");
return -1;
}
//把出队的画面显示出来
//YUYV转换成ARGB,液晶屏不可以直接显示YUYV数据
allyuyvtoargb((char *)(array[i].start),argbbuf);
for(j=0; j<H; j++)
memcpy(lcdmem+80+j*800,&argbbuf[j*W],W*4); //每一次拷贝一行像素点
//入队
ret=ioctl(camerafd,VIDIOC_QBUF,&vbuf);
if(ret==-1)
{
perror("申请入队失败了!\n");
return -1;
}
}
return 0;
}
//关闭摄像头
int mycamera::camera_uninit()
{
int i,ret;
//收尾
ret=ioctl(camerafd,VIDIOC_STREAMOFF,&mytype);
if(ret==-1)
{
perror("关闭摄像头失败了!\n");
return -1;
}
close(camerafd);
close(lcdfd);
munmap(lcdmem,800*480*4);
for(i=0; i<4; i++)
munmap(array[i].start,array[i].len);
return 0;
}
mycamera::mycamera() {}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "mycamera.h"
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
void fun();
void on_pushButton_2_clicked();
private:
Ui::MainWindow *ui;
QTimer *timer;
mycamera *yourcamera;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建摄像头对象
yourcamera = new mycamera();
//初始化摄像头
yourcamera->camera_init();
//创建定时器对象
timer = new QTimer(this);
//设置定时器间隔时间
timer->setInterval(10);
//关联信号
connect(timer,SIGNAL(timeout()),this,SLOT(fun()));
}
MainWindow::~MainWindow()
{
delete ui;
}
//启动摄像头
void MainWindow::on_pushButton_clicked()
{
timer->start();
}
//槽函数
void MainWindow::fun()
{
yourcamera->camera_captrue();
}
void MainWindow::on_pushButton_2_clicked()
{
timer->stop();
}
方法二实现:
mycamera.h 里继承 QThread 类
mycamea.cpp
定一个标志位控制线程退出
int flag=1; //用来控制线程结束的标志位
mainwindow.h
去掉定时器相关的函数即可
#include "mainwindow.h"
#include "ui_mainwindow.h"
extern int flag; //声明外部全局变量
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建摄像头对象
yourcamera = new mycamera();
//初始化摄像头
yourcamera->camera_init();
}
MainWindow::~MainWindow()
{
//关闭摄像头
yourcamera->camera_uninit();
delete ui;
}
//启动摄像头
void MainWindow::on_pushButton_clicked()
{
flag=1;
//start方法开启一个线程并自动执行run
yourcamera->start();
}
void MainWindow::on_pushButton_2_clicked()
{
//终止线程--》开发板采用这种方法无法终止线程
//yourcamera.terminate();
//修改标志位flag,把flag改成0
flag=0;
//回收线程
yourcamera->wait();
}