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

简介: 一、综述    如何采集图片?在windows环境下,我们可以使用dshow,在linux下,也有ffmpeg等基础类库,再不济,opencv自带的videocapture也是提供了基础的支撑。那么在andoird下,使用的肯定是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、编写拍照代码
//打开摄像头,采集图片
void MainWindow :: on_btn_capture_pressed ()
{
ui -> lbMain -> setScaledContents ( true ); //显示的图像自动缩放
b_canSave = false //图片没有采集完成,目前不可以保存
//引用JNI
QAndroidJniEnvironment env ;
//创建用于打开摄像头的content
QAndroidJniObject action = QAndroidJniObject :: fromString ( "android.media.action.IMAGE_CAPTURE" ); QAndroidJniObject   ( intent ( "android/content/Intent" , "(Ljava/lang/String;)V" , action . object < jstring >());
//设定img路径
QString date = QDateTime :: currentDateTime (). toString ( "yyyyMMdd_hhmmss" );
QAndroidJniObject fileName = QAndroidJniObject :: fromString ( date + ".jpg" );
QAndroidJniObject savedDir = QAndroidJniObject :: callStaticObjectMethod ( "android/os/Environment" , "getExternalStorageDirectory" , "()Ljava/io/File;" );
//使用CHECK_EXCEPTION处理异常
CHECK_EXCEPTION ()
qDebug () << "savedDir - " << savedDir . toString ();
QAndroidJniObject savedImageFile ( "java/io/File" , "(Ljava/io/File;Ljava/lang/String;)V" , savedDir . object < jobject >(), fileName . object < jstring >());
CHECK_EXCEPTION ()
qDebug () << "savedImageFile - " << savedImageFile . toString ();
QAndroidJniObject savedImageUri = QAndroidJniObject :: callStaticObjectMethod ( "android/net/Uri" , "fromFile" , "(Ljava/io/File;)Landroid/net/Uri;" ,
savedImageFile . object < jobject >());
CHECK_EXCEPTION ()
 
//将输出路径传递过来
QAndroidJniObject mediaStoreExtraOutput = 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 = new ResultReceiver ( savedImageFile . toString (), ui -> lbMain );
startActivity ( intent , 1 , resultReceiver );
//获得返回的绝对地址(注意这句话一定要写在 CHECK_EXCEPTION 中)
strFetchImage = savedImageFile . toString ();
}
最终采集到的图片地址保存在 strFetchImage  
 
e、编写处理代码。由于我这里主要进行的是图像处理操作,所以必须结合OpenCV相关函数进行
//图像处理操作
void MainWindow :: on_btn_process_pressed ()
{
b_canSave = false ;
if (strFetchImage != "" )
{
ui -> lbMain -> setScaledContents ( false );
Mat src = imread (strFetchImage. toStdString ());
Mat src2 ;
Mat rotated ;
////////////////////////////主要算法/////////////////////////////
cv :: resize ( src , src2 , cv :: Size ( 720 , 1000 )); //标准大小
Mat src_gray ;
Mat src_all = src2 . clone ();
 
Mat threshold_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 ( "开始寻找轮廓! 开始筛选轮廓!" );
}
 
//轮廓筛选
int c = 0 , ic = 0 , area = 0 ;
int parentIdx =- 1 ;
for ( int i = 0 ; i < contours .size(); i ++ )
{
//hierarchy[i][2] != -1 表示不是最外面的轮廓
if ( hierarchy [ i ][ 2 ] != - 1 && ic == 0 )
{
parentIdx = i ;
ic ++;
}
else if ( hierarchy [ i ][ 2 ] != - 1 )
{
ic ++;
}
//最外面的清0
else if ( 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 ( int i = 0 ; i < contours2 .size(); i ++)
drawContours ( src_all , contours2 , i , CV_RGB ( 0 , 255 , 0 ) , - 1 );
 
//识别出来了关键区域,但是数量不对,显示当前识别结果,退出循环
if ( contours2 .size() != 4 )
{
QPixmap qpixmap = Mat2QImage ( src_all );
ui -> lbMain -> setPixmap ( qpixmap );
ui -> lb_info -> setText ( "定位点数量不为4!请重新采集数据。" );
return ;
}
else
{
//否则,进一步分割
Point point [ 4 ];
for ( int i = 0 ; i < contours2 .size(); i ++)
{
//筛选轮廓,
double d = contourArea ( contours2 [ i ]);
if ( d > 720 * 1000 / 4 )
{
ui -> lb_info -> setText ( "采集中有错误轮廓,请重新采集数据" );
QPixmap qpixmap = Mat2QImage ( src_all );
ui -> lbMain -> setPixmap ( qpixmap );
return ;
}
//定位重点,并重新排序
Point ptmp = Center_cal ( contours2 , i );
 
if ( ptmp . x < 720 / 4 && ptmp . y < 1000 / 4 )
{
point [ 0 ] = ptmp ;
}
else if ( ptmp . x < 720 / 4 && ptmp . y > 1000 / 4 )
{
point [ 2 ] = ptmp ;
}
else if ( ptmp . x > 720 / 4 && ptmp . y < 1000 / 4 )
{
point [ 1 ] = ptmp ;
}
else
{
point [ 3 ] = ptmp ;
}
}
 
//打印出来
for ( int i = 0 ; i < 3 ; i ++)
{
char cbuf [ 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 :: Point2f src_vertices [ 4 ];
src_vertices [ 0 ] = point [ 0 ];
src_vertices [ 1 ] = point [ 1 ];
src_vertices [ 2 ] = point [ 2 ];
src_vertices [ 3 ] = point [ 3 ];
Point2f dst_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 );
Mat warpMatrix = getPerspectiveTransform ( src_vertices , dst_vertices );
//执行透视变化
warpPerspective ( src2 , rotated , warpMatrix , rotated . size (), INTER_LINEAR , BORDER_CONSTANT );
}
//////////////////////////END 主要算法 END ///////////////////////
// 将图片显示到label上
QPixmap qpixmap = Mat2QImage ( rotated );
ui -> lbMain -> setPixmap ( qpixmap );
matResult = rotated . clone ();
b_canSave = true ;
}
}
三、初步结果和继续研究需要解决的问题
按照设计,目前得到这样的结果
下一步注重解决以下问题
1、提高程序稳定性;
2、提高界面流程性和运行速度;
3、重构代码,进一步进行封装;
4、添加数据保存的相关功能。
感谢阅读至此,希望有所帮助!
 





目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com
相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
1月前
|
openCL 开发工具 C语言
OpenCV 图像处理学习手册:6~7
OpenCV 图像处理学习手册:6~7
58 0
|
1月前
|
存储 编解码 算法
OpenCV 图像处理学习手册:1~5
OpenCV 图像处理学习手册:1~5
17 0
|
1月前
|
安全 Go
Go语言并发:释放程序潜能的魔力
Go语言并发:释放程序潜能的魔力
12 0
|
1月前
|
机器学习/深度学习 存储 文字识别
Qt5 和 OpenCV4 计算机视觉项目:1~5(5)
Qt5 和 OpenCV4 计算机视觉项目:1~5(5)
33 0
|
2月前
|
缓存 算法 计算机视觉
OpenCV图像处理-视频分割静态背景-MOG/MOG2/GMG
1.概念介绍 视频背景扣除原理:视频是一组连续的帧(一幅幅图组成),帧与帧之间关系密切(GOP/group of picture),在GOP中,背景几乎是不变的,变的永远是前景。
51 0
|
18天前
|
人工智能 Linux API
OpenCV这么简单为啥不学——1.1、图像处理(灰度图、模糊图片、GaussianBlur函数、提取边缘、边缘膨胀、边缘细化)
OpenCV这么简单为啥不学——1.1、图像处理(灰度图、模糊图片、GaussianBlur函数、提取边缘、边缘膨胀、边缘细化)
25 0
|
29天前
|
C++
QT第一个程序命名空间详解,解释ui_widget的和xxx.cpp的联系
QT第一个程序命名空间详解,解释ui_widget的和xxx.cpp的联系
23 0
|
1月前
|
编译器
QT creator开发环境下 界面更改后运行程序不能实时更新或者在源文件添加该控件后无法编译的问题
在使用QT Creator开发界面的过程中,偶尔会出现添加控件后,运行程序后,界面控件无法更新的情况,或者在源文件使用该控件却出现无法编译的情况,使用QT Creator 4.8.2也会出现这个情况,也不知道这种情况会不会在以后有所改善。
19 0
|
2天前
|
IDE 开发工具
QT案例IDE编写 -- 新建和保存文件及退出程序
QT案例IDE编写 -- 新建和保存文件及退出程序
4 0
|
3天前
|
编解码 算法 自动驾驶
探索OpenCV:图像处理的利器
探索OpenCV:图像处理的利器
18 0

相关产品

  • 云迁移中心