欢迎光临
我们一直在努力

OpenCV实现视频采集

一:Mat 图像容器

Mat是OpenCV 里装图像的 “盒子”,将图像的像素数组 + 宽度 + 高度 + 格式信息全部打包在一起。

1.Mat 里存了什么?

一张图片 = 一堆像素点Mat 就是存这些像素:

  • cols:宽度
  • rows:高度
  • channels:通道数(1 = 灰度,3 = 彩色,4 = 带透明)
  • data:真正的像素数据(uchar*)
  • type:图像格式(CV_8UC1 / CV_8UC3…)

2.常用的Mat格式

1.CV_8UC1

8 位无符号,1 通道 → 灰度图

2.CV_8UC3

8 位无符号,3 通道 → 彩色图   注意:OpenCV 默认是 BGR 顺序

3.CV_8UC4

8 位无符号,4 通道 → BGRA 带透明

3.mat最常用的操作

1.imread 读取图像

Mat img = imread("test.jpg"); // 读取图片
Mat frame; // 空图

2.判断空图

if (img.empty()) {
// 图片加载失败
}

3.获取宽,高,通道数

int w = img.cols;
int h = img.rows;
int ch = img.channels();

4.复制图像

Mat a = img.clone(); // 深拷贝(安全)
Mat b = img; // 浅拷贝(共用数据)

5.imshow 显示图像

Mat img = imread("test.jpg");
imshow("窗口标题", img); // 显示
waitKey(0); // 等待按键(必须写,不然一闪而过)

注意:只能在主线程中调用,子线程中不会生效。

6.imwrite 保存图像

Mat frame; // 你的图像

// 保存到当前目录
imwrite("保存.jpg", frame);

// 保存到指定路径
imwrite("/home/liu/图片/保存.png", frame);

二:图像颜色格式

0.转换函数

// 源图像,目标图像,转换码
cvtColor(src, dst, 码表);

1.BGR(OpenCV默认)

  • 顺序:蓝 绿 红
  • 来源:OpenCV 天生就是 BGR
  • 用途:读图、摄像头、YOLO 推理

Mat img = imread("a.jpg"); // 默认 BGR

2.BGRA(带透明通道)

  • 4 通道
  • BGR + 透明(Alpha)
  • 透明图片用(PNG)
  • 用途:贴图、水印、带透明的纹理

cvtColor(mat, mat, COLOR_BGR2BGRA);

3.RGB(Qt/OpenGL/ 屏幕显示专用)

  • 3 通道
  • 顺序:红 绿 蓝
  • Qt、图片浏览器、屏幕 都用 RGB
  • 你必须转成 RGB 才能正常显示

cvtColor(mat, mat, COLOR_BGR2RGB);

4.GRAY(灰度图)

  • 1 通道
  • 只有亮度,没有颜色
  • 黑→灰→白
  • 用途:去色、识别、压缩

cvtColor(mat, mat, COLOR_BGR2GRAY);

5.HSV(颜色识别专用)

  • 3 通道:色调、饱和度、亮度
  • 用途:根据颜色找物体(红 / 黄 / 蓝物体跟踪)
  • 比 RGB/BGR 更适合识别颜色

cvtColor(mat, mat, COLOR_BGR2HSV);

6./YUV(摄像头原始格式)

  • 摄像头输出最常见格式
  • Y = 亮度,UV = 颜色
  • 常见:YUYV、UYVY、NV12、NV21
  • 必须转 BGR/RGB 才能显示

cvtColor(mat, mat, COLOR_YUV2BGR_YUYV);

7.综合对比表

格式通道特点谁在用
BGR 3 OpenCV 默认 OpenCV、YOLO
BGRA 4 带透明 PNG、纹理
RGB 3 正常颜色 Qt、OpenGL、屏幕
GRAY 1 黑白 去色、识别
HSV 3 颜色识别 颜色跟踪
YUV 2/3 摄像头原始 摄像头、RTSP

三:Mat转换为QImage

Qt中界面显示一般都采用QImage容器,不能直接显示Mat容器,

Mat是OpenCV的图像格式,QImage是Qt的图像格式

Mat的颜色顺序是BGR,QImage的颜色顺序的RGB

/**
* @brief 万能 Mat 转 QImage(支持所有常见格式)
* @param mat 输入 OpenCV Mat(支持 CV_8UC1 CV_8UC3 CV_8UC4)
* @return 输出 Qt QImage(可直接显示/上传OpenGL)
*/
QImage matToQImage(const cv::Mat &mat)
{
// 1. 空图直接返回空
if (mat.empty())
return QImage();

// 2. 获取宽高
int width = mat.cols;
int height = mat.rows;

// 3. 根据 Mat 类型转 QImage
switch (mat.type()) {

// ==========================
// 灰度图 CV_8UC1
// ==========================
case CV_8UC1: {
QImage img(mat.data, width, height, mat.step, QImage::Format_Grayscale8);
return img.copy(); // 深拷贝,安全
}

// ==========================
// 彩色图 BGR → RGB(最重要)
// ==========================
case CV_8UC3: {
cv::Mat rgb;
cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); // 必须转!
QImage img(rgb.data, width, height, rgb.step, QImage::Format_RGB888);
return img.copy();
}

// ==========================
// 带透明通道 BGRA → RGBA
// ==========================
case CV_8UC4: {
QImage img(mat.data, width, height, mat.step, QImage::Format_RGBA8888);
return img.copy();
}

// 不支持的格式
default:
return QImage();
}
}

四:VideoCapture 视频捕获

VideoCapture = 视频 / 摄像头的 “读取器”,支持:

1.打开摄像头

cap.open(0); // 0=默认摄像头

cap.open(-1); //自动搜索

cap.open(0, cv::CAP_V4L2); //Linux下强制 V4L2 防止阻塞

2.本地视频

cap.open("test.mp4"); // 视频

3.网络视频流

cap.open("rtsp://..");// 网络流

4. 判断是否打开成功

if (!cap.isOpened()) {
qDebug() << "打开失败!";
}

5. 读一帧图像

Mat frame;
cap.read(frame);

6.关闭摄像头

// 关闭摄像头,释放硬件
cap.release();

7.读取宽,高,帧率

// 宽度
int w = cap.get(CAP_PROP_FRAME_WIDTH);

// 高度
int h = cap.get(CAP_PROP_FRAME_HEIGHT);

// 帧率
double fps = cap.get(CAP_PROP_FPS);

8.摄像头线程标准写法

void CameraThread::run()
{
VideoCapture cap;
cap.open(0); // 打开摄像头

if (!cap.isOpened()) {
qDebug() << "摄像头打开失败!";
return;
}

Mat frame;
while (m_running)
{
cap.read(frame);
if (frame.empty()) continue;

// 发给界面显示
emit frameReady(frame);

msleep(30);//精准 30fps
}

cap.release();
}

为什么不采用定时器捕获?

① 定时器精度差:QTimer 最小只能到 15ms

② 定时器依附主线程事件循环:界面一滑动、绘图、加载东西,定时器直接被卡住,延迟

③ 无法做到真正实时:摄像头是连续不断输出数据定时器是间隔性去拿→ 必然丢帧

五:简易视频播放器(QLabel版本+OpenGL版本)

1.效果图

显示图片

显示本地视频

打开本地摄像头        

打开网络视频流

以下是几个可用测试地址(可能有延迟,打开后需要等几秒钟):

rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp

rtmp://ns8.indexforce.com/home/mystream

rtmp://ns8.indexforce.com/home/mystream

2.整体思路

1.现在设计师界面拖入一个QLabel和一个QOpenGLWidget,在下方按钮添加几个想要的功能。

QOpenGLWdiget需要提升至一个类种用于实现画面渲染。

2.捕获视频或网络流在子线程中进行,然后确定多少帧率取一次视频帧,将这个视频帧通过信号槽传递给主线程中进行画面的更新,打开图片可直接在主线程中传递到对应控件上显示即可,主线程专门复制渲染或者绘制(事件循环)。

3.OpenCV模块工程文件配置:

# 自动找 OpenCV
find_package(OpenCV REQUIRED)

# 包含头文件
include_directories(${OpenCV_INCLUDE_DIRS})

#链接库
target_link_libraries(项目名 PRIVATE ${OpenCV_LIBS})

3.核心部分代码

1.捕获线程:

#ifndef CAPTURETHREAD_H
#define CAPTURETHREAD_H

#include <QThread>
#include <QImage>
#include <QTimer>

//捕获线程支持的数据源类型
enum class CapType
{
camera,//相机
localvideo,//本地视频
urlVideo//网络视频流
};

class CaptureThread : public QThread
{
Q_OBJECT
public:
CaptureThread(CapType type);
void run()override;
public slots:
void ExitCap();
void SetSource(QString source);//图片,视频,url
signals:
void SendImg(QImage img);
private:
CapType m_capType;
bool m_running;
QString m_source;
};

#endif // CAPTURETHREAD_H

#include "CaptureThread.h"
#include <opencv4/opencv2/opencv.hpp>
#include <QDebug>
using namespace cv;
/**
* @brief 万能 Mat 转 QImage(支持所有常见格式)
* @param mat 输入 OpenCV Mat(支持 CV_8UC1 CV_8UC3 CV_8UC4)
* @return 输出 Qt QImage(可直接显示/上传OpenGL)
*/

QImage matToQImage(const cv::Mat &mat)
{
// 1. 空图直接返回空
if (mat.empty())
{
return QImage();
}
// 2. 获取宽高
int width = mat.cols;
int height = mat.rows;

// 3. 根据 Mat 类型转 QImage
switch (mat.type()) {

case CV_8UC1: {
QImage img(mat.data, width, height, mat.step, QImage::Format_Grayscale8);
return img.copy(); // 深拷贝,安全
}

case CV_8UC3: {
cv::Mat rgb;
cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); // 必须转!
QImage img(rgb.data, width, height, rgb.step, QImage::Format_RGB888);
return img.copy();
}
case CV_8UC4: {
QImage img(mat.data, width, height, mat.step, QImage::Format_RGBA8888);
return img.copy();
}

default:
return QImage();
}
}

CaptureThread::CaptureThread(CapType type):
m_running(true),
m_capType(type)
{
}

void CaptureThread::SetSource(QString source)
{
m_source=source;
}

void CaptureThread::ExitCap()
{
m_running=false;
}
void CaptureThread::run()
{
VideoCapture cap;
if(m_capType==CapType::camera)
{
cap.open(0);
}
else
{
cap.open(m_source.toStdString());
}
if(!cap.isOpened())
{
qDebug()<<"打开失败";
return;
}
Mat frame;
while(m_running)
{
cap.read(frame);
if(frame.empty())//没有数据了,退出
{
break;
}
emit SendImg(matToQImage(frame));
msleep(30);//30fps
}
cap.release();
}

2.GLWidget渲染部分:

#ifndef VIDEOWIDGET_H
#define VIDEOWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>

#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
class VideoWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
VideoWidget(QWidget *parent=nullptr);
~VideoWidget();
void initializeGL();
void resizeGL(int w,int h);
void paintGL();
public slots:
void updateFrame(QImage img);
private:
void initShader();
QOpenGLShaderProgram *m_shader;

void initTexture();
QOpenGLTexture* m_texture;

void initContainer();
QOpenGLBuffer *m_vbo;
QOpenGLVertexArrayObject *m_vao;
};

#endif // VIDEOWIDGET_H

#include "VideoWidget.h"
#include <QImage>
VideoWidget::VideoWidget(QWidget *parent):QOpenGLWidget(parent) {}
VideoWidget::~VideoWidget()
{}
void VideoWidget::initializeGL()
{
initializeOpenGLFunctions();
initShader();
initTexture();
initContainer();
}
void VideoWidget::resizeGL(int w,int h)
{
}
void VideoWidget::paintGL()
{
glClearColor(1.0,1.0,1.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);

m_shader->bind();
m_vao->bind();
m_texture->bind();

glDrawArrays(GL_TRIANGLE_FAN,0,4);
}

void VideoWidget::initShader()
{
const char* vertexShader=R"(
#version 330 core
layout(location=0) in vec2 aPos;
layout(location=1) in vec2 aTexCoord;
out vec2 texCoord;
void main()
{
gl_Position=vec4(aPos,0.0,1.0);
texCoord=aTexCoord;
}
)";

const char* fragmentShader=R"(
#version 330 core
in vec2 texCoord;
out vec4 fragColor;
uniform sampler2D tex;
void main()
{
fragColor=texture(tex,texCoord);
}
)";

m_shader=new QOpenGLShaderProgram(this);
m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader);
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShader);
m_shader->link();
}
void VideoWidget::initTexture()
{
m_texture=new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
}
void VideoWidget::updateFrame(QImage img)
{
//如果图像尺寸变了,必须销毁旧纹理
if (m_texture && (m_texture->width() != img.width() || m_texture->height() != img.height()))
{
qDebug() << "调整尺寸";
m_texture->destroy();
delete m_texture;
m_texture = nullptr;
}

// 纹理不存在则重新创建
if (!m_texture)
{
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->create();
m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
}

// 直接更新数据,不再设置格式/大小
m_texture->setData(img.mirrored(false, true));

update();
}
void VideoWidget::initContainer()
{
float vertex[]={
-1.0,1.0, 0.0,1.0,
1.0,1.0, 1.0,1.0,
1.0,-1.0, 1.0,0.0,
-1.0,-1.0, 0.0,0.0
};

m_vao=new QOpenGLVertexArrayObject(this);
m_vao->create();
m_vao->bind();
m_vbo=new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vbo->create();
m_vbo->bind();
m_vbo->allocate(vertex,sizeof(vertex));
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)(sizeof(float)*2));
glEnableVertexAttribArray(1);
m_vbo->release();
m_vao->release();
}

3.更新图片帧槽函数:

void Widget::showFrame(QImage img)
{
if (img.isNull()) return;
ui->lblVideo->setPixmap(QPixmap::fromImage(img).scaled(ui->lblVideo->size(), Qt::KeepAspectRatio));//QLabel显示
ui->openGLWidget->updateFrame(img);//OpenGL 显示
}

赞(0)
未经允许不得转载:171主机测评 » OpenCV实现视频采集
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址