opencv视频采集封装qml

3 篇文章 1 订阅

这段时间由于项目需要,要做银行卡识别。所以从很早开始就开始了解opencv了,而项目的界面框架主要是qml实现的,

这时就需要将opencv封装成qml控件。我们都知道qml是c++的扩展,opencv原本就是用c++实现的,所以这个并不是很难。

qml绘图的基元素是Item,Item是由QuickItem实现的。所以这里我们应该从QuickItem入手。为简单易用,我写成了qml插件

的形式。

第一步:新建一个qml插件工程


我使用的名字如下

次往后点,在下面这个界面时注意一下,就可以了

二步:在.pro文件中导入opencv的库,我的opencv库有contrib模块,所以有点多


第三步:编写子线程代码。在打开cardidentificationitem.cpp之后,我们在构造函数中可以看到下面这些话


说明我们只要重写updatePaintNode()这个函数就可以了。我们都知道GUI线程是不能够阻塞的,所以这里应该很容易想到另起一个线程;

以信号槽的方式来实现,子线程执行opencv的操作,将图片转化后发送到主线程显示。这里将不介绍qml的基础知识,不懂的可以先到网上补

补;以下是cardidentificationitem.h


我的子线程类是CameraThread。


这个类比较简单,主要就是重写run()函数,上面红框部分是我自己在中心做的一个扫描显示框的一些参数和摄像头采集帧的长、宽。

为什么要加一个中心扫描框呢?那是因为我要做的是一个银行卡识别,所以加个框更好。

我们的原是图片是Mat类型要显示的话要QImage,这里我们需要有一个转换

QImage CameraThread::toQImage(Mat & srcFrom)
{
    Mat rgbFrame;
    cvtColor(srcFrom,rgbFrame,CV_BGR2RGB);
    QImage img((const uchar*)rgbFrame.data,rgbFrame.cols,rgbFrame.rows,rgbFrame.step,QImage::Format_RGB888);
    img.bits(); //深拷贝,避免线程退出内存错误
    return img;
}
接下来就是run函数了
void CameraThread::run()
{
    assert(VIDEOWIDTH > BOXWIDTH && VIDEOHEIGHT > BOXHEIGHT);

    VideoCapture capture(0);

    if(!capture.isOpened()){
        emit sigError(QStringLiteral("打开摄像头失败,请检查是否安装摄像头设备"));
        return;
    }

    capture.set(CV_CAP_PROP_FRAME_WIDTH,VIDEOWIDTH);
    capture.set(CV_CAP_PROP_FRAME_HEIGHT,VIDEOHEIGHT);

    double frameWidth = capture.get(CV_CAP_PROP_FRAME_WIDTH);
    double frameHight = capture.get(CV_CAP_PROP_FRAME_HEIGHT);

    int boxX = (frameWidth - BOXWIDTH)/2;
    int boxY = (frameHight - BOXHEIGHT)/2;

    rect = Rect(boxX,boxY,BOXWIDTH,BOXHEIGHT);
    //创建掩码矩阵
    Mat frame,alphaROI,RGBFrame;
    //在opencv中定位文本坐标
//    String text(FRAMETEXT);
//    int fontScale = 1,thick=2,baseLine=0;
//    Size textSize = getTextSize(text,FONT_HERSHEY_PLAIN,fontScale,thick,&baseLine);
//    Point org((frameWidth- textSize.width)/2,(frameHight-textSize.height)/2);

    while(!isInterruptionRequested()){
        capture >> frame;
        //方法一:设置银行卡矩形框以外的亮度较暗,扫描框较亮
//        alphaROI = frame(Rect(0,0,frameWidth,boxY));
//        alphaROI.convertTo(alphaROI,-1,0.6,0);
//        alphaROI = frame(Rect(0,boxY,boxX,BOXHEIGHT));
//        alphaROI.convertTo(alphaROI,-1,0.6,0);
//        alphaROI = frame(Rect(boxX + BOXWIDTH,boxY,boxX,BOXHEIGHT));
//        alphaROI.convertTo(alphaROI,-1,0.6,0);
//        alphaROI = frame(Rect(0,boxY + BOXHEIGHT,frameWidth,boxY));
//        alphaROI.convertTo(alphaROI,-1,0.6,0);
        //方法二:
        frame.convertTo(frame,-1,0.5,0);
        alphaROI = frame(rect);
        alphaROI.convertTo(alphaROI,-1,2,0);

        line(frame,Point(boxX - VIDEOLINEWIDTH,boxY),Point(boxX - VIDEOLINEWIDTH,boxY + VIDEOLABELVER),Scalar(0,255,0),VIDEOLINEWIDTH);
        line(frame,Point(boxX - VIDEOLINEWIDTH,boxY - VIDEOLINEWIDTH),Point(boxX + BOXWIDTH,boxY - VIDEOLINEWIDTH),Scalar(0,255,0),VIDEOLINEWIDTH);
        line(frame,Point(boxX + BOXWIDTH + 1,boxY),Point(boxX + BOXWIDTH + 1,boxY + VIDEOLABELVER),Scalar(0,255,0),VIDEOLINEWIDTH);
        line(frame,Point(boxX - VIDEOLINEWIDTH,boxY  + BOXHEIGHT - VIDEOLABELVER),Point(boxX - VIDEOLINEWIDTH,boxY + BOXHEIGHT),Scalar(0,255,0),VIDEOLINEWIDTH);
        line(frame,Point(boxX - VIDEOLINEWIDTH,boxY  + BOXHEIGHT + 1),Point(boxX + BOXWIDTH,boxY  + BOXHEIGHT + 1),Scalar(0,255,0),VIDEOLINEWIDTH);
        line(frame,Point(boxX + BOXWIDTH + 1,boxY + BOXHEIGHT - VIDEOLABELVER),Point(boxX + BOXWIDTH + 1,boxY + BOXHEIGHT),Scalar(0,255,0),VIDEOLINEWIDTH);
        //在opencv中添加文本
//        putText(frame,text,org,FONT_HERSHEY_PLAIN,fontScale,Scalar(0,255,0),thick);
        //画出文本所在的矩形框
//        rectangle(frame, org + Point(0, baseLine),
//                  org + Point(textSize.width, -textSize.height),
//                  Scalar(0,255,0));
        //画出文本除去字体粗细后的下基准线
//        line(frame, org + Point(0, thick),
//             org + Point(textSize.width, thick),
//             Scalar(0, 0, 255));
        //转换格式,并发给GUI显示
        emit sigImage(toQImage(frame));

        waitKey(30);

        MatchTemp matchBank(alphaROI,imread(TEMPLATIMG));
        Rect m_rect = matchBank.match(CV_TM_SQDIFF);
        if(m_rect.width == 0){
            qDebug() << QStringLiteral("请插入银行卡");
            continue;
        }

        qDebug() << QStringLiteral("已检测到银行卡") << m_rect.x << m_rect.y;
        PerspectiveCorrect perspective(alphaROI);
        perspective.correct(alphaROI);
        emit sigImage(toQImage(frame)); //发送给主线程
    }
}
第四步:编写主线程:在主线程中,首先要连接子线程的信号,将数据保存到私有变量中,之前的构造函数变成了这样
CardIdentificationItem::CardIdentificationItem(QQuickItem *parent):
    QQuickItem(parent)
{
    // By default, QQuickItem does not draw anything. If you subclass
    // QQuickItem to create a visual item, you will need to uncomment the
    // following line and re-implement updatePaintNode()

    // setFlag(ItemHasContents, true);
    setFlag(Flag::ItemHasContents,true);
    thr = new CameraThread(this);
    qRegisterMetaType<QImage>("QImage&");
    connect(thr,SIGNAL(sigImage(QImage&)),this,SLOT(setCurFrame(QImage&)));
    connect(thr,SIGNAL(sigError(QString)),this,SLOT(slotError(QString)));
}
保存数据到私有变量

void CardIdentificationItem::setCurFrame(QImage &imgFrom)
{
    _img = imgFrom;
    update();
    emit curFrameChanged(); //响应qml信号
}
void CardIdentificationItem::slotError(QString str)
{
    _error = str;
    thr->quit();
    thr->wait();
    emit errorComing(); //响应qml信号
}
重写updatePaintNode()函数
QSGNode* CardIdentificationItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
    QSGSimpleTextureNode *texture = static_cast<QSGSimpleTextureNode *>(node);
    if (!texture) {
        texture = new QSGSimpleTextureNode();
    }

    if(_img.isNull()){
        _img = QImage(boundingRect().size().toSize(), QImage::Format_RGB888);
    }

    QSGTexture *sgTexture = window()->createTextureFromImage(_img.scaled(boundingRect().size().toSize()));
    if(sgTexture){
        QSGTexture *tt = texture->texture();
        if(tt){
            tt->deleteLater();
        }
        texture->setRect(boundingRect());
        texture->setTexture(sgTexture);
    }
    return texture;
}
我们还需要启动和终止子线程的方法

void CardIdentificationItem::setRunning(bool flag)
{
    _running = flag;
    if(_running)
        thr->start();
    else{
        thr->requestInterruption();
        thr->wait();
    }
    emit runngingChanged();
}
在晰构函数中,我们要终止子线程

CardIdentificationItem::~CardIdentificationItem()
{
    thr->requestInterruption();
    thr->wait();
}
第五步:构建、编译:这里我的测试工程为一个纯的qml界面工程,需要release版本,所以直接编译了release版本。

第六步:配置插件:在qt的安装路径下,找到以下路径并创建目录、复制文件


在新建的目录中,将编译生成的dll我qmldir复制过来

第七步:测试:新建一个qml UI工程,代码如下


由于我要做银行卡检测,用的是模板匹配,需要一张模板图片,所以这里有一个temp.jpg

以下是运行截图。


这个应用里面还有另外两个类,一个是用于模板匹配的,另外一个是图像矫正的;有兴趣的可以研究一下。

工程代码已上传到我的资源,点此下载编译时,请将.po文件中的opencv库路径,改为你自己的opencv库路径







  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: QML是一种基于C++的用户界面描述语言,用于开发跨平台的图形用户界面(GUI)。它可以与OpenCV(开放源代码的计算机视觉库)结合使用来采集USB视频。 在QML中使用OpenCV,首先需要将OpenCV库添加到项目中,并使用C++与QML进行交互。使用OpenCV的VideoCapture类,可以轻松地采集来自USB相机的视频流。在采集视频时,可以指定设备的编号(例如0、1、2等),以便从多个相机中采集。 通过将QMLOpenCV结合使用,可以实现更好、更实用的用户界面和功能,在图像和视频处理方面得到更高的自由度和灵活性。采集来自USB摄像头的视频也是常见的应用场景,例如监视和安防等。 ### 回答2: QML是一种跨平台的快速界面构建语言,而OpenCV是一种开源的计算机视觉库,结合起来可以实现在QML界面上实时显示USB摄像头的视频流。 首先需要在C++中通过OpenCV调用USB摄像头,并将视频流获取到QML界面上。可以通过定义QML对象和C++对象之间的调用来实现这一功能。在QML中定义一个VideoPlayer对象,通过C++中定义的VideoCapture类获取USB摄像头视频流,并将其传递给VideoPlayer对象。 代码示例: ``` // C++代码 void VideoCapture::captureVideo() { cv::VideoCapture cap(0); if (!cap.isOpened()) { qDebug() << "Cannot open the USB camera!"; return; } while (run) { cv::Mat frame; cap.read(frame); if (frame.empty()) { break; } QImage img(frame.data, frame.cols, frame.rows, QImage::Format_RGB888); emit captured(img); } } // QML代码 VideoPlayer { id: videoPlayer anchors.fill: parent onCaptured: { videoPlayer.source = image; videoPlayer.show(); } } ``` 在此示例中,VideoCapture类定义了一个captureVideo()方法来从USB相机中获取视频帧,并将其转换为QImage格式,并发射captured()信号。VideoPlayer对象则定义了一个onCaptured()槽函数来接收captured()信号,并将所捕获的帧传递给VideoPlayer对象显示。 总的来说,这种结合QMLOpenCV的方法可以方便地实现在QML界面上显示USB摄像头视频流的目的,使得开发人员可以利用现有的开源库来提供功能,同时使得用户体验更加流畅和自然。 ### 回答3: QMLOpenCV是两个非常强大的工具,可用于处理图像和视频数据。当需要从USB摄像头采集视频时,这两个工具可以很好地协同工作。 QML是一种用于创建用户界面的语言。它提供了一种简单的方式来设计并创建各种应用程序界面。在处理视频数据时,QML可以使用Qt Multimedia组件来访问视频数据流。 而OpenCV是一个广泛使用的计算机视觉库,它具有各种功能,包括图像和视频处理功能。OpenCV提供了许多API来获取,处理和显示图像和视频数据。 要使用QMLOpenCV采集USB视频,需要以下步骤: 1. 首先,使用QML来创建应用程序用户界面。 2. 使用OpenCV中提供的API来连接USB摄像头并开始采集视频数据。 3. 获取流数据并将其传递给QML界面以显示视频。 4. 使用OpenCV的功能来对视频数据进行分析和处理,例如面部识别或运动跟踪。 5. 在QML界面中添加控件以控制摄像头和视频流,例如开始/停止录制视频、调整摄像头设置等。 在这个过程中,需要熟悉OpenCVQML的API和功能,以便最大限度地利用它们的优势。因此,建议有一定的编程经验和计算机视觉知识的人员进行该过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值