模板匹配,多尺度,多角度,android,arm64-v8a,armeabi-v7a

最近在Android上折腾模板匹配的时候踩了不少坑,发现这玩意儿比想象中难伺候。特别是手机摄像头拍出来的图像,目标物体可能忽大忽小还带旋转,这时候传统的单尺度模板匹配直接扑街。今天就跟大伙唠唠怎么用多尺度+多角度的组合拳搞定这个难题,顺便说说armeabi和arm64那些架构上的破事。

先上点硬核的——用OpenCV搞个图像金字塔。这玩意儿就像叠罗汉,把原图一层层缩小,方便咱们在不同尺度上找目标:

vector<Mat> buildPyramid(Mat src, int levels) {
    vector<Mat> pyramid;
    pyramid.push_back(src);
    for(int i=1; i<levels; i++){
        Mat down;
        pyrDown(pyramid[i-1], down);
        pyramid.push_back(down);
    }
    return pyramid;
}

这段代码看着简单,但有个坑爹的地方:ARM架构的NEON指令加速。之前在armeabi-v7a上跑得好好的,换到arm64-v8a就发现速度不对劲。后来用ndk-stack抓log才发现,某些旧版的OpenCV库在64位架构下没优化到位,换成v4.5之后的版本才正常。

角度处理更让人头大,直接旋转模板比旋转输入图像省资源。但要注意旋转后的黑边会影响匹配结果,得搞个掩膜处理:

public static Mat rotateTemplate(Mat template, double angle) {
    Point center = new Point(template.cols()/2.0, template.rows()/2.0);
    Mat rotMat = Imgproc.getRotationMatrix2D(center, angle, 1.0);
    Mat rotated = new Mat();
    Imgproc.warpAffine(template, rotated, rotMat, template.size(), 
        Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, new Scalar(0));
    return rotated;
}

在真机上实测发现,同样的算法在arm64设备上能比armeabi快30%左右,特别是当角度采样间隔小于10度的时候。不过内存占用会暴涨,红米Note系列这种3GB内存的老机器容易OOM,得做动态分辨率适配。

模板匹配,多尺度,多角度,android,arm64-v8a,armeabi-v7a

说到架构兼容,build.gradle里得这么配才稳妥:

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        }
    }
}

遇到过最诡异的问题是v7a和v8a的SIMD指令对齐方式不同,导致同样的C++代码在两种架构下匹配结果有微小差异。后来在预处理阶段统一加了边界填充才解决。

最后来个压箱底的并行处理技巧——用RenderScript把不同尺度的匹配任务分到多核:

val script = ScriptC_hybrid(rs)
val inputAllocation = Allocation.createFromBitmap(rs, inputBitmap)
val outputAllocation = Allocation.createFromBitmap(rs, outputBitmap)

script.forEach_scaleTask(inputAllocation, outputAllocation)
outputAllocation.copyTo(outputBitmap)

这种骚操作能让红魔游戏手机这类8核设备吃满CPU,不过得注意老设备的核心调度问题。实测在三星S20上处理640x480的图能跑到15fps,基本满足实时需求。

模板匹配这玩意儿就像谈恋爱,既要全面撒网(多尺度多角度),又要精准打击(相似度阈值)。下次再遇到旋转的二维码或者变形的logo,别急着甩锅给算法,说不定加个金字塔循环就能起死回生。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐