(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、編寫拍照代碼
//打開攝像頭,采集圖片
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、添加數據保存的相關功能。
感謝閱讀至此,希望有所幫助!
 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM