前言

显而易见

显而易见, 不管接入 openCV 来做什么工作, 其最明显的就是包大小会直线上升。 其中 opoenCV Android 的 so 动态库 v7、 v8 版本的大小加上有 34.4 MB, 也就是大小至少会增加 34.4 MB。 其中当前 app 包大小为 38.14 MB, 也就是 app 包会增加将近一倍。

想法实验

通过对目标的分析, 接入相机计算图片的压盖比例。 其中隐含的包含了几点:

  • 接入相机, 来判断前后两帧压盖比例, 其必须要求 openCV 处理的极快。
  • 如果仅仅找两个图片测相似度,如果是如找茬类分析两个图片的不同点, 一一对应的话, 那么是不能满足压盖比例需求呢。
    那么其初步就是为了解决这两个问题。 对原本两点的分析, 那么我们去拼接两个前后两帧, 然后计算拼接后的面积和原始两帧的面积和的比例可以近似的计算出来呢。 先暂定这个方案, 那么需要需要解决的就是第一点了, 我们仅仅是为了计算两帧的面积和及拼接后的面积, 那么我们是否可以把前后两帧等比例缩小后在进行拼接呢。
    先说下结论, 其中我们要的速度和拼接较好效果。 其中把原图缩小后会提高拼接速度, 但是太小的话会识别不出来, 所以我们需要调整好这个数值。 还有就是 openCV 在设置参数的时候可以设置拼接的模糊找关键点的大小, 当然越大拼接的越好, 但时间会越长。 不能接受超过 200 ms 的拼接速度。

实现

openCV c++ Switcher 拼接代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
extern "C"
JNIEXPORT jobject JNICALL
Java_cn_xiaoxige_opencvdemo2_MainActivity_tmpStitchBitMap2(JNIEnv *env, jobject thiz,
jobjectArray bitmaps, jfloat matchConf,
jboolean isNeedMatInfo) {

jclass resultClass = env->FindClass("cn/xiaoxige/opencvdemo2/entity/StitchBitmapResult");
jmethodID initMethodId = env->GetMethodID(resultClass, "<init>", "()V");
jobject result = env->NewObject(resultClass, initMethodId);

jfieldID codeFieldId = env->GetFieldID(resultClass, "code", "I");
jfieldID widthFieldId = env->GetFieldID(resultClass, "width", "I");
jfieldID heightFieldId = env->GetFieldID(resultClass, "height", "I");
jfieldID matAddress = env->GetFieldID(resultClass, "matAddress", "J");

try {
vector<Mat> mats;
int size = env->GetArrayLength(bitmaps);
mats.reserve(size);
for (int i = 0; i < size; ++i) {
Mat mat;
if (bitmapToMatrix(env, env->GetObjectArrayElement(bitmaps, i), mat)) {
mats.push_back(mat);
}
}
Ptr<Stitcher> stitcher = Stitcher::create();
if (stitcher == nullptr) {
env->SetIntField(result, codeFieldId, -1);
return result;
}

//stitcher.setRegistrationResol(0.6);
// stitcher.setWaveCorrection(false);
/*=match_conf默认是0.65,我选0.8,选太大了就没特征点啦,0.8都失败了*/
auto *matcher = new detail::BestOf2NearestMatcher(true, matchConf);
stitcher->setFeaturesMatcher(matcher);
stitcher->setFeaturesFinder(new detail::OrbFeaturesFinder());
stitcher->setBundleAdjuster(new detail::BundleAdjusterRay());
stitcher->setSeamFinder(new detail::NoSeamFinder);
//曝光补偿
stitcher->setExposureCompensator(new detail::NoExposureCompensator());
stitcher->setBlender(new detail::FeatherBlender());

Mat *mat = new Mat();
Stitcher::Status state = stitcher->stitch(mats, *mat);
env->SetIntField(result, codeFieldId, (jint) state);
if (state == Stitcher::Status::OK) {
env->SetIntField(result, widthFieldId, mat->cols);
env->SetIntField(result, heightFieldId, mat->rows);
if (isNeedMatInfo) {
env->SetLongField(result, matAddress, (jlong) mat);
}
}
if (!isNeedMatInfo) {
delete mat;
}
return result;
} catch (...) {
env->SetIntField(result, codeFieldId, -1);
return result;
}
}

Java 处理相机处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
private fun getTargetBitmap(imageProxy: ImageProxy): Pair<Bitmap, Bitmap>? {
val image = imageProxy.image ?: return null

val bitmap: Bitmap = Bitmap.createBitmap(
// 必须使用最终图片的大小, 最终大小可能跟期望有所差距。
imageProxy.width, imageProxy.height, Bitmap.Config.ARGB_8888)
YuvToRgbConverter(this@CameraTestActivity).yuvToRgb(image, bitmap)
val matrix = Matrix()
// matrix.setRotate(imageProxy.imageInfo.rotationDegrees.toFloat())
matrix.postScale(0.1f, 0.1f)
val targetBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
return Pair<Bitmap, Bitmap>(bitmap, targetBitmap)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
private inner class CameraxDataHandle : CameraxExtension.ICameraxDataHandle {

private var preBitmap: Bitmap? = null

private var backCount = 0
private var saveCount = 0
private var stitchErrorFrameCount = 0

private var errorSaveCount = 0
private var normalSaveCount = 0

private var isEnd = false

override fun cameraxDataHandleInit(params: List<Any>): Boolean {
return true
}

override fun cameraxDataHandleStart(): Boolean {
isEnd = false
backCount = 0
saveCount = 0
stitchErrorFrameCount = 0
errorSaveCount = 0
normalSaveCount = 0
return true
}

@SuppressLint("UnsafeOptInUsageError")
override fun cameraxDataHandleDataBack(imageProxy: ImageProxy): Boolean {
val startTime = System.currentTimeMillis()
val bitmapResult = getTargetBitmap(imageProxy) ?: return false
val targetBitmap = bitmapResult.second

++backCount

if (preBitmap == null) {
preBitmap = targetBitmap
Log.e("xiaoxige", "对比结果: 0")
runOnUiThread {
if (isEnd) return@runOnUiThread
ivImg1.setImageBitmap(targetBitmap)
ivImg2.setImageBitmap(targetBitmap)
ivImg3.setImageBitmap(targetBitmap)
tvContent.text = "拼接结果: 0"
}
++saveCount
++normalSaveCount
saveBitmap(bitmapResult.first)
bitmapResult.first.recycle()
return true
}

// 比较对比
val result = MainActivity.sMainActivity.tmpStitchBitMap2(arrayOf(targetBitmap, preBitmap!!), 0.5f, true)
var stitichBitmap: Bitmap? = null
if (result.code == 0) {
stitichBitmap = Bitmap.createBitmap(result.width, result.height, Bitmap.Config.ARGB_8888)
MainActivity.sMainActivity.getBitmapByMatAddress(result.matAddress, stitichBitmap)
}

val rate = if (result.code != 0) 0 else {
((result.width * result.height).toFloat() / (targetBitmap.width * targetBitmap.height + preBitmap!!.width * preBitmap!!.height)) * 100
}.toInt()

runOnUiThread {
if (isEnd) return@runOnUiThread
ivImg1.setImageBitmap(preBitmap)
ivImg2.setImageBitmap(targetBitmap)
if (stitichBitmap != null) {
ivImg3.setImageBitmap(stitichBitmap)
} else {
ivImg3.setImageResource(0)
}

tvContent.text =
"拼接结果: ${result.code}, (${targetBitmap.width}, ${targetBitmap.height} => (${result.width}, ${result.height})), 拼接占比: $rate %, 耗时: ${System.currentTimeMillis() - startTime}ms"

Log.e("xiaoxige",
"拼接结果: ${result.code}, (${targetBitmap.width}, ${targetBitmap.height} => (${result.width}, ${result.height})), 拼接占比: $rate %, 耗时: ${System.currentTimeMillis() - startTime}ms")
}

if (result.code != 0) {
stitchErrorFrameCount++
}

if ((result.code == 0 && rate > 45) || stitchErrorFrameCount > 10) {
preBitmap = targetBitmap
++saveCount
saveBitmap(bitmapResult.first)
if (stitchErrorFrameCount > 0) {
errorSaveCount++
} else {
normalSaveCount++
}
Log.e("xiaoxige", if (stitchErrorFrameCount > 0) "错误保存" else "正常保存")
stitchErrorFrameCount = 0
}

bitmapResult.first.recycle()
return true
}

override fun cameraxDataHandleEnd(isSuccessEnd: Boolean): Boolean {
tvContent.text =
"作业已结束, 其中照片总数: $backCount, 保存照片总数: $saveCount (${normalSaveCount}/${errorSaveCount})。 共耗时: ${endTimeA - startTimeA}ms"
Log.e("xiaoxige",
"作业已结束, 其中照片总数: $backCount, 保存照片总数: $saveCount (${normalSaveCount}/${errorSaveCount})。 共耗时: ${endTimeA - startTimeA}ms")
backCount = 0
saveCount = 0
isEnd = true
return true
}

}