3. Qt 控制摄像头

这一章我们来简单了解一下摄像头的工作原理以及如何使用Qt的API来控制摄像头。

Qt多媒体API提供了许多与摄像头相关的类,使用这些类和API可以很容易从移动设备摄像头或网络摄像头访问图像和视频。

在开始控制摄像头之前,我们先使用 gstreamer 命令来测一下摄像头是否可用。

3.1. 摄像头

光线通过镜头Lens进入摄像头内部,然后经过IR Filter过滤红外光,最后到达sensor(传感器), senor分为按照材质可以分为CMOS和CCD两种,可以将光学信号转换为电信号,再通过内部的ADC电路转换为数字信号, 然后传输给DSP(如果有的话,如果没有则以DVP的方式传送数据到基带芯片baseband,此时的数据格式Raw Data)加工处理, 转换成RGB、YUV等格式输出。

Lens

在相机组件的一端是透镜组件(一个或多个透镜,布置成将光聚焦到传感器上)。 镜头本身有时可以移动来调整焦距和变焦,或者它们可以固定在一个安排中, 以便在焦距和成本之间提供一个很好的平衡。

一些镜头组件可以自动调整,以便与相机不同距离的物体可以保持对焦。 这通常是通过测量镜框特定区域的锐度来实现的,并且通过调整透镜组件直到它达到最大锐度。 在某些情况下,相机将始终使用该帧的中心。其他相机也可以允许指定聚焦区域(用于“触摸缩放”或“面部缩放”特征)。

传感器

一旦光到达传感器,它就被转换成数字像素。这个过程可以依赖于很多事情,但最终归结为两件事-多久转换允许采取,以及如何明亮的光。 转换时间越长,质量就越好。使用闪光灯有助于让更多的光线照射到传感器上,使其能够更快地转换像素,在相同的时间内提供更好的质量。 相反,只要相机稳定,允许较长的转换时间可以让您在较暗的环境中拍照。

图像处理

传感器捕获图像后,相机固件对其执行各种图像处理任务,以补偿各种传感器特性、当前照明和所需的图像特性。 更快的传感器像素转换时间往往会引入数字噪声,因此可以根据相机传感器设置进行一定量的图像处理以消除这种噪声。

图像的颜色也可以在这个阶段进行调整,以补偿不同的光源-闪光灯和阳光给同一物体提供非常不同的效果, 因此可以根据图片的白平衡来调整图像(由于光源的不同色温)。 在此阶段还可以执行某些形式的“特殊效果”。可以产生黑白,棕褐色或“负”样式的图像。

后期处理

最后,一旦一个完美的聚焦,曝光和处理后的图像已经创建,它可以被我们所用。 例如,相机图像可以由应用程序代码进一步处理(例如,检测条形码,或将全景图像缝合在一起),或保存为JPEG等通用格式, 或用于创建电影。在Qt中,有许多这样的类可以帮组我们完成这样的工作。

3.2. GStreamer

GStreamer 是用来构建流媒体应用的开源多媒体框架(framework),其目标是要简化音/视频应用程序的开发, 目前已经能够被用来处理像 MP3、Ogg、MPEG1、MPEG2、AVI、Quicktime 等多种格式的多媒体数据。

我们使用GStream是为了方便快速的测试摄像头。 鲁班猫摄像头测试,请参考 https://doc.embedfire.com/linux/stm32mp1/quick_start_guide/zh/latest/quick_start/camera/camera.html

gst-launch-1.0 命令参考 https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c

3.3. 相关类

Qt中使用相机主要会用到这四个类:QCamera、QCameraViewfinder、QMediaRecorder、QCameraImageCapture

QCameraViewfinder一起用于取景器显示,QMediaRecorder用于视频记录,QCameraImageCapture用于图像拍摄。

3.3.1. QCameraInfo

QCameraInfo列出可用的摄像机并选择要使用的摄像机。

mainwindow.h :linenos:
const QList<QCameraInfo> availableCameras = QCameraInfo::availableCameras();
for (const QCameraInfo &cameraInfo : availableCameras) {
    if (cameraInfo == QCameraInfo::defaultCamera())
        qDebug()<<"defaultCamera ok";
}
mainwindow.h :linenos:
QScopedPointer<QCamera> m_camera;
m_camera.reset(new QCamera(cameraInfo));

这样我们就有了一个相机实例。

3.3.2. QCameraViewfinder

QCameraViewfinder类提供了一个相机取景器窗口,用法如下:

mainwindow.h :linenos:
camera = new QCamera;

viewfinder = new QCameraViewfinder();
viewfinder->show();

camera->setViewfinder(viewfinder);

3.3.3. QMediaRecorder

QMediaRecorder类是一个高级媒体录制类。它和其他媒体对象(如QRadioTuner或QCamera)配合使用实现录像功能。其用法如下:

mainwindow.h :linenos:
recorder = new QMediaRecorder(camera);

QAudioEncoderSettings audioSettings;
audioSettings.setCodec("audio/amr");
audioSettings.setQuality(QMultimedia::HighQuality);

recorder->setAudioSettings(audioSettings);

recorder->setOutputLocation(QUrl::fromLocalFile(fileName));
recorder->record();

3.3.4. QCameraImageCapture

QCameraImageCapture类是高级图像记录类。它和其他媒体对象(例如QCamera)配合使用实现拍照功能。其用法如下:

mainwindow.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
camera = new QCamera;

viewfinder = new QCameraViewfinder();
viewfinder->show();

camera->setViewfinder(viewfinder);

imageCapture = new QCameraImageCapture(camera);

camera->setCaptureMode(QCamera::CaptureStillImage);
camera->start();

//on half pressed shutter button
camera->searchAndLock();

//on shutter button pressed
imageCapture->capture();

//on shutter button released
camera->unlock();

3.4. 例程说明

野火提供的Qt Demo已经开源,仓库地址在:

文档所涉及的示例代码也提供下载,仓库地址在:

本章例程在 embed_qt_develop_tutorial_code/Camera

分别使用 GST命令、QCameraViewfinder、QAbstractVideoSurface 实现

以 Camera_Viewfinder 为例

第一步,初始化一个QCamera设备; 其次,初始化一个相机取景器,用来预览摄像头画面; 再者,使用QCameraImageCapture图片记录器拍照; 最后使用信号和槽来关联用户的操作。

通过类 QCameraInfo 来获取当前终端的摄像头设备。

camerawidget.cpp
1
2
3
4
5
6
7
8
9
void CameraWidget::on_timeout()
{
    const QList<QCameraInfo> availableCameras = QCameraInfo::availableCameras();
    for (const QCameraInfo &cameraInfo : availableCameras) {
        if (cameraInfo == QCameraInfo::defaultCamera())
            qDebug()<<"defaultCamera ok";
    }
    InitCamera(QCameraInfo::defaultCamera());
}

初始化摄像头,取景器,图片记录器,关联其中的信号和槽。

camerawidget.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void CameraWidget::InitCamera(const QCameraInfo &cameraInfo)
{
    m_camera.reset(new QCamera(cameraInfo));
    connect(m_camera.data(), QOverload<QCamera::Error>::of(&QCamera::error), this, &CameraWidget::displayCameraError);

    if (NULL != m_camera) {
        //surface = new QtVideoWidgetSurface(this);
        //surface = new QCameraViewfinder(this);

        m_imageCapture.reset(new QCameraImageCapture(m_camera.data()));

        connect(m_imageCapture.data(), &QCameraImageCapture::readyForCaptureChanged, this, &CameraWidget::readyForCapture);
        //connect(m_imageCapture.data(), &QCameraImageCapture::imageCaptured, this, &CameraWidget::processCapturedImage);
        connect(m_imageCapture.data(), &QCameraImageCapture::imageSaved, this, &CameraWidget::imageSaved);
        connect(m_imageCapture.data(), QOverload<int, QCameraImageCapture::Error, const QString &>::of(&QCameraImageCapture::error),this, &CameraWidget::displayCaptureError);


        surface = new QtViewFinder(this);
        connect(surface,SIGNAL(returnbtn_clicked_signal()),this, SLOT(on_clieckBackhome()));
        connect(surface,SIGNAL(take_picture_signal()),this, SLOT(on_takePicture()));
        surface->setGeometry(0,0,this->width(),this->height());
        surface->setEnabledTake(false);

        m_camera->setViewfinder(surface);

        QCameraViewfinderSettings set;
        set.setResolution(1280, 720);
        m_camera->setViewfinderSettings(set);

        surface->show();
        qDebug()<<surface->width()<<surface->height();

//        QCameraViewfinderSettings set = m_camera->viewfinderSettings();
//        qDebug()<<set.resolution().height()<<set.resolution().width();
        surface->setSize(surface->width(),surface->height());

        m_camera->start();
    }
}

QCameraViewfinderSettings 用于指定QCamera使用的取景器设置我们设置分辨率为1280*720。

拍照则调用 m_imageCapture->capture(strFilePath+strFileName), strFilePath+strFileNamew 为存储路径。

camerawidget.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CameraWidget::on_takePicture()
{
    //获取程序当前运行目录
    QString strFilePath = QCoreApplication::applicationDirPath();
    strFilePath+="/photos/";
    QString strFileName;
    strFileName += QDateTime::currentDateTime().toString("yyyyMMddhhmss");
    strFileName += ".png";
    m_imageCapture->capture(strFilePath+strFileName);

}
build001
  • Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行

  • LubanCat 选择 ebf_lubancat,只编译

注意 当两种构建套件进行切换时,请重新构建项目或清除之前项目。

build002

直接点击编译并运行程序,结果如下:

result001
result001

编译程序之后,需要将程序拷贝到LubanCat开发板中,可通过NFS或SCP命令

NFS环境搭建 参考这里

SCP命令如下:

# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path

编译好的程序在 embed_qt_develop_tutorial_code/app_bin/sql 目录中

scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/camera/Camera_Viewfinder /usr/local/qt-app/

在运行程序之前先确保安装了gst的库。

注意

  • 当开发板使用了lubancat-carp-qt版本镜像,默认已经安装有ebf-gst库,因为与全功能gst库冲突,不要再额外安装后者,如果要安装全功能gst库,需要先sudo apt remove ebf-gst。

  • 当开发板使用lubancat-carp-console镜像时,可以选择安装以下其一。

  • 当使用NAND核心板的开发板时,存储容量小,使用ebf-gst库,不适合安装全功能gst库。

# ebf-gst 是我们利用yocto裁剪过的gst库
sudo apt-get install ebf-gst
# 或者 安装官方全功能的gst库
sudo apt install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools

在LubanCat运行程序

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Camera_Viewfinder
result002