一、綜述
如何采集圖片?在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、添加數據保存的相關功能。
感謝閱讀至此,希望有所幫助!