• (GO_GTD_2)基于OpenCV和QT,建立Android图像处理程序


    一、综述

        如何采集图片?在windows环境下,我们可以使用dshow,在linux下,也有ffmpeg等基础类库,再不济,opencv自带的videocapture也是提供了基础的支撑。那么在andoird下,使用的肯定是Android自带的相关函数了。由于Android是基于java语言的,如果我们想要调用Android 的相关函数,那么必须通过JNI的方法。
        这里有可以分为两种,一种是直接在java中实现比较完整的函数,在qt中,只需要调用这个函数就可以;另一种就是使用qt自带的jni机制,比如下面这样,打开摄像头,并且采集图片。我们首先介绍第二种方法,让大家最快进入情况。
     
    二、通过JNI打开摄像头
    a、填加头文件和命名空间,定义公共变量和宏:
    #include <QtAndroid>
    #include <QDebug>
    #include <QAndroidJniEnvironment>
    #include <QAndroidActivityResultReceiver>
    #include <QDateTime>
    #include <QFile>
    using namespace cv;
    using namespace QtAndroid;
     
    QString strFetchImage = "";
    QString selectedFileName = "";
     
    #define CHECK_EXCEPTION() 
    if(env->ExceptionCheck())
    {
    qDebug() << "exception occured";
    env->ExceptionClear();
    }
     
    其中需要注意的是,CHECK_EXCEPTION是用来检查Android系统是否有异常的。这一点在使用JNI的时候非常重要和必要。
     
    b、填加回调类,主要就是在一系列异常判断后,获得imagepath。该类集成自ResultReceiver
    class ResultReceiver: public QAndroidActivityResultReceiver
    {
        public: ResultReceiver(QString imagePath, QLabel *view) : m_imagePath(imagePath), m_imageView(view){}
        void handleActivityResult(int receiverRequestCode,int resultCode,const QAndroidJniObject & data){
         qDebug() << "handleActivityResult, requestCode - " << receiverRequestCode<< " resultCode - " << resultCode<< " data - " << data.toString();
        if(resultCode == -1 && receiverRequestCode == 1){
        qDebug() << "captured image to - " << m_imagePath;
        qDebug() << "captured image exist - " << QFile::exists(m_imagePath);
        m_imageView->setPixmap(QPixmap(m_imagePath));}
        }
        QString m_imagePath;
        QLabel *m_imageView;
    };
     
    C、填加控件触发事件。一般来说我们选择pressed事件
    d、编写拍照代码
    //打开摄像头,采集图片
    voidMainWindow::on_btn_capture_pressed()
    {
    ui->lbMain->setScaledContents(true);//显示的图像自动缩放
    b_canSave=false//图片没有采集完成,目前不可以保存
    //引用JNI
    QAndroidJniEnvironmentenv;
    //创建用于打开摄像头的content
    QAndroidJniObjectaction=QAndroidJniObject::fromString("android.media.action.IMAGE_CAPTURE");QAndroidJniObject (intent("android/content/Intent","(Ljava/lang/String;)V",action.object<jstring>());
    //设定img路径
    QStringdate=QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    QAndroidJniObjectfileName=QAndroidJniObject::fromString(date+".jpg");
    QAndroidJniObjectsavedDir=QAndroidJniObject::callStaticObjectMethod("android/os/Environment","getExternalStorageDirectory","()Ljava/io/File;");
    //使用CHECK_EXCEPTION处理异常
    CHECK_EXCEPTION()
    qDebug()<<"savedDir-"<<savedDir.toString();
    QAndroidJniObjectsavedImageFile("java/io/File","(Ljava/io/File;Ljava/lang/String;)V",savedDir.object<jobject>(),fileName.object<jstring>());
    CHECK_EXCEPTION()
    qDebug()<<"savedImageFile-"<<savedImageFile.toString();
    QAndroidJniObjectsavedImageUri=QAndroidJniObject::callStaticObjectMethod("android/net/Uri","fromFile","(Ljava/io/File;)Landroid/net/Uri;",
    savedImageFile.object<jobject>());
    CHECK_EXCEPTION()
     
    //将输出路径传递过来
    QAndroidJniObjectmediaStoreExtraOutput=QAndroidJniObject::getStaticObjectField("android/provider/MediaStore","EXTRA_OUTPUT","Ljava/lang/String;");
    CHECK_EXCEPTION()
    qDebug()<<"MediaStore.EXTRA_OUTPUT-"<<mediaStoreExtraOutput.toString();
    intent.callObjectMethod(
    "putExtra","(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",mediaStoreExtraOutput.object<jstring>(),
    savedImageUri.object<jobject>());
     
    //获得采集图片的绝对路径,并且显示出来
    ResultReceiver*resultReceiver=newResultReceiver(savedImageFile.toString(),ui->lbMain);
    startActivity(intent,1,resultReceiver);
    //获得返回的绝对地址(注意这句话一定要写在CHECK_EXCEPTION中)
    strFetchImage=savedImageFile.toString();
    }
    最终采集到的图片地址保存在strFetchImage 
     
    e、编写处理代码。由于我这里主要进行的是图像处理操作,所以必须结合OpenCV相关函数进行
    //图像处理操作
    voidMainWindow::on_btn_process_pressed()
    {
    b_canSave=false;
    if(strFetchImage!="")
    {
    ui->lbMain->setScaledContents(false);
    Matsrc=imread(strFetchImage.toStdString());
    Matsrc2;
    Matrotated;
    ////////////////////////////主要算法/////////////////////////////
    cv::resize(src,src2,cv::Size(720,1000));//标准大小
    Matsrc_gray;
    Matsrc_all=src2.clone();
     
    Matthreshold_output;
    vector<vector<Point>>contours,contours2;
    vector<Vec4i>hierarchy;
    //预处理
    cvtColor(src2,src_gray,CV_BGR2GRAY);
    blur(src_gray,src_gray,Size(3,3));//模糊,去除毛刺
    threshold(src_gray,threshold_output,100,255,THRESH_OTSU);
    //添加提示
    ui->lb_info->setText("开始寻找轮廓!");
    //寻找轮廓
    //第一个参数是输入图像2值化的
    //第二个参数是内存存储器,FindContours找到的轮廓放到内存里面。
    //第三个参数是层级,**[Next,Previous,First_Child,Parent]**的vector
    //第四个参数是类型,采用树结构
    //第五个参数是节点拟合模式,这里是全部寻找
    findContours(threshold_output,contours,hierarchy,CV_RETR_TREE,CHAIN_APPROX_NONE,Point(0,0));
    //添加提示
    if(contours.size()<=10)
    {
        ui->lb_info->setText("轮廓筛选错误,循环退出!请重新采集数据。");
        return;
    }
    else
    {
        ui->lb_info->setText("开始寻找轮廓!开始筛选轮廓!");
    }
     
    //轮廓筛选
    intc=0,ic=0,area=0;
    intparentIdx=-1;
    for(inti=0;i<contours.size();i++)
    {
    //hierarchy[i][2]!=-1表示不是最外面的轮廓
    if(hierarchy[i][2]!=-1&&ic==0)
    {
    parentIdx=i;
    ic++;
    }
    elseif(hierarchy[i][2]!=-1)
    {
    ic++;
    }
    //最外面的清0
    elseif(hierarchy[i][2]==-1)
    {
    ic=0;
    parentIdx=-1;
    }
    //找到定位点信息
    if(ic>=2)
    {
    contours2.push_back(contours[parentIdx]);
    ic=0;
    parentIdx=-1;
    }
    }
     
    //添加提示
    if(contours2.size()<3)
    {
    ui->lb_info->setText("定位点选择错误,循环退出!请重新采集数据。");
    return;
    }
    else
    {
    ui->lb_info->setText("开始寻找轮廓!开始筛选轮廓!定位点选择正确!");
    }
     
    //填充定位点,我们约定,必须要能够同时识别出4个点来
    for(inti=0;i<contours2.size();i++)
    drawContours(src_all,contours2,i,CV_RGB(0,255,0),-1);
     
    //识别出来了关键区域,但是数量不对,显示当前识别结果,退出循环
    if(contours2.size()!=4)
    {
    QPixmapqpixmap=Mat2QImage(src_all);
    ui->lbMain->setPixmap(qpixmap);
    ui->lb_info->setText("定位点数量不为4!请重新采集数据。");
    return;
    }
    else
    {
    //否则,进一步分割
    Pointpoint[4];
    for(inti=0;i<contours2.size();i++)
    {
    //筛选轮廓,
    doubled=contourArea(contours2[i]);
    if(d>720*1000/4)
    {
    ui->lb_info->setText("采集中有错误轮廓,请重新采集数据");
    QPixmapqpixmap=Mat2QImage(src_all);
    ui->lbMain->setPixmap(qpixmap);
    return;
    }
    //定位重点,并重新排序
    Pointptmp=Center_cal(contours2,i);
     
    if(ptmp.x<720/4&&ptmp.y<1000/4)
    {
    point[0]=ptmp;
    }
    elseif(ptmp.x<720/4&&ptmp.y>1000/4)
    {
    point[2]=ptmp;
    }
    elseif(ptmp.x>720/4&&ptmp.y<1000/4)
    {
    point[1]=ptmp;
    }
    else
    {
    point[3]=ptmp;
    }
    }
     
    //打印出来
    for(inti=0;i<3;i++)
    {
    charcbuf[100];
    sprintf(cbuf,"%d",i+1);
    putText(src_all,cbuf,point[i],FONT_HERSHEY_PLAIN,5,Scalar(0,0,0),5);
    ui->lb_info->setText("结果识别正确,可以保存");
    }
     
    //透视变换
    cv::Point2fsrc_vertices[4];
    src_vertices[0]=point[0];
    src_vertices[1]=point[1];
    src_vertices[2]=point[2];
    src_vertices[3]=point[3];
    Point2fdst_vertices[4];
    dst_vertices[0]=Point(0,0);
    dst_vertices[1]=Point(720,0);
    dst_vertices[2]=Point(0,1000);
    dst_vertices[3]=Point(720,1000);
    MatwarpMatrix=getPerspectiveTransform(src_vertices,dst_vertices);
    //执行透视变化
    warpPerspective(src2,rotated,warpMatrix,rotated.size(),INTER_LINEAR,BORDER_CONSTANT);
    }
    //////////////////////////END主要算法END///////////////////////
    //将图片显示到label上
    QPixmapqpixmap=Mat2QImage(rotated);
    ui->lbMain->setPixmap(qpixmap);
    matResult=rotated.clone();
    b_canSave=true;
    }
    }
    三、初步结果和继续研究需要解决的问题
    按照设计,目前得到这样的结果
    下一步注重解决以下问题
    1、提高程序稳定性;
    2、提高界面流程性和运行速度;
    3、重构代码,进一步进行封装;
    4、添加数据保存的相关功能。
    感谢阅读至此,希望有所帮助!
     





  • 相关阅读:
    操作系统——第四章 文件管理
    操作系统——第三章 内存管理
    操作系统——第二章 进程管理
    last-child到底怎么用
    Https个人总结
    白话https
    RSA算法
    算法导论笔记:11散列表(哈希表)
    算法导论笔记:10基本数据结构(番外)
    算法导论笔记:09中位数和顺序统计量
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/8286475.html
Copyright © 2020-2023  润新知