一,综述
如何采集图片?在 windows 环境下,我们可以使用 dshow,在 linux 下,也有 ffmpeg 等基础类库,再不济,opencv 自带的 videocapture 也是提供了基础的支撑.那么在 andoird 下,使用的肯定是 Android 自带的相关函数了.由于 Android 是基于 java 语言的,如果我们想要调用 Android 的相关函数,那么必须通过 JNI 的方法.
这里有可以分为两种,一种是直接在 java 中实现比较完整的函数,在 qt 中,只需要调用这个函数就可以;另一种就是使用 qt 自带的 jni 机制,比如下面这样,打开摄像头,并且采集图片.我们首先介绍第二种方法,让大家最快进入情况.
二,通过 JNI 打开摄像头
a,填加头文件和命名空间,定义公共变量和宏:
"exception
#include
#include
#include
#include
#include
#include
using
namespace
cv;
using
namespace
QtAndroid
;
QString
strFetchImage
=
""
;
QString
selectedFileName
=
""
;
#define
CHECK_EXCEPTION
()
\
if(env->ExceptionCheck())\
{\
qDebug
()
<<
occured"
其中需要注意的是,CHECK_EXCEPTION 是用来检查 Android 系统是否有异常的.这一点在使用 JNI 的时候非常重要和必要.
;\
env->ExceptionClear();\
}
b,填加回调类,主要就是在一系列异常判断后,获得 imagepath.该类集成自 ResultReceiver:
"captured
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
()
<<
image
to
-
""captured
<<
m_imagePath
;
qDebug
()
<<
image
exist
-
"C,填加控件触发事件.一般来说我们选择 pressed 事件
<<
QFile
::
exists
(
m_imagePath
);
m_imageView
->
setPixmap
(
QPixmap
(
m_imagePath
));
}
}
QString
m_imagePath
;
QLabel
*
m_imageView
;
};
d,编写拍照代码
"savedDir
// 打开摄像头,采集图片
void
MainWindow
::
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());
// 设定 img 路径
QString
date
=
QDateTime
::
currentDateTime
().
toString
(
"yyyyMMdd_hhmmss"
);
QAndroidJniObject
fileName
=
QAndroidJniObject
::
fromString
(
date
+
".jpg"
);
QAndroidJniObjectsavedDir=QAndroidJniObject::callStaticObjectMethod("android/os/Environment","getExternalStorageDirectory","()Ljava/io/File;");
// 使用 CHECK_EXCEPTION 处理异常
CHECK_EXCEPTION()
qDebug
()
<<
-
"
"savedImageFile
<<
savedDir
.
toString
();
QAndroidJniObjectsavedImageFile("java/io/File","(Ljava/io/File;Ljava/lang/String;)V",savedDir.object(),fileName.object());
CHECK_EXCEPTION()
qDebug
()
<<
-
"
"MediaStore.EXTRA_OUTPUT
<<
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
()
<<
-
"
最终采集到的图片地址保存在
<<
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 相关函数进行
的 vector
// 图像处理操作
void
MainWindow
::
on_btn_process_pressed
()
{
b_canSave
=
false
;
if
(strFetchImage
!=
""
)
{
ui
->
lbMain
->
setScaledContents
(
false
);
Mat
src
=
imread
(strFetchImage.
toStdString
());
Matsrc2;
Matrotated;
//////////////////////////// 主要算法 /////////////////////////////
cv
::
resize
(
src
,
src2
,
cv
::
Size
(
720
,
1000
));
// 标准大小
Matsrc_gray;
Mat
src_all
=
src2
.
clone
();
Matthreshold_output;
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]**
" 开始寻找轮廓!
// 第四个参数是类型,采用树结构
// 第五个参数是节点拟合模式,这里是全部寻找
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,添加数据保存的相关功能.
来源: https://www.cnblogs.com/jsxyhelu/p/8286475.html