Android OpenCV 图像融合

在 Android 手机上合成图片也不麻烦,使用 OpenCV 图像无缝融合功能,通过少量的操作就可以将两副图像无缝的融合到一起,在要求不是很精确的场合,有着可以接受的合成效果。

合成图片

上图是在 Android 手机上运行 OpenCV seamlessClone 合成的一张图片,seamlessClone 方法直接调用 Native 的实现。参考 C++ API 文档,我将该方法的声明和参数一起写在下方。

public static void seamlessClone(Mat src, Mat dst, Mat mask, Point point, Mat blend, int flags)
// Mat src:   Input 8-bit 3-channel image.
// Mat dst:   Input 8-bit 3-channel image.
// Mat mask:  Input 8-bit 1 or 3-channel image.
// Point point:   Point in dst image where object is placed.
// Mat blend: Output image with the same size and type as dst.
// int flags: Cloning method

其中,参与合成的两幅图为 srcdstsrc 一般比较小它将融合进 dst 成为其一部分。mask 可以用来选取 src 进行融合的区域,可以是 src 的一部分也可以是全部。point 控制 src 的中心点位于 dst 中的位置,即在 dst 哪个位置进行融合。blend 是运算后的输出,它需要和 dst 一样大。flags 告诉 OpenCV 如何融合图像,目前 Android OpenCV 支持两种融合方式 Photo.NORMAL_CLONEPhoto.MIXED_CLONE

合成封面图的原始图片在下方,分别为 src(普通图片)和 dst(影片《飞鹰艾迪》截图)。创建一个 mask 选择 src 中人物区域融合进 dst,设置 point摆放到右侧的位置即是封面图的效果。封面的融合方式 flagPhoto.NORMAL_CLONE,另外一种是 Photo.MIXED_CLONE 稍后将做演示。

图 src,普通图片

图 dst,取自影片:Eddie the Eagle / 飞鹰艾迪,左侧是狼叔

在一个 Activity 读取上面两幅图进行融合,并且在布局中显示。布局文件如下,从上到下 4 个 ImageView 分别显示 srcmaskdstblend

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        tools:ignore="ContentDescription"
        android:id="@+id/main_imageview_src"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin="2dp"
        android:scaleType="fitCenter"/>

    <ImageView
        tools:ignore="ContentDescription"
        android:id="@+id/main_imageview_mask"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin="2dp"
        android:scaleType="fitCenter"
        android:visibility="gone"/>

    <ImageView
        tools:ignore="ContentDescription"
        android:id="@+id/main_imageview_dst"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin="2dp"
        android:scaleType="fitCenter" />

    <ImageView
        tools:ignore="ContentDescription"
        android:id="@+id/main_imageview_blend"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin="2dp"
        android:scaleType="fitCenter" />
</LinearLayout>

Java 代码,首先是读取原图并设置显示。然后创建 mask 选择人物区域,数据是事先准备好的,当然也可以放到 Android App 里完成,不过这不是本文的重点。结果中的黑白影像就是 mask,区域数据比较粗糙,你会发现人物几个部位会有点模糊。最后创建 blend 调用 Photo.seamlessClone 得到融合输出,结果显示于下面的图中。文中图片均经过一定比例缩小,和代码的数据不能匹配。

Mat matSrc = Imgcodecs.imread(pathImageSrc);
Mat matDst = Imgcodecs.imread(pathImageDst);
displayMat(matSrc, vImageSrc);
displayMat(matDst, vImageDst);

Point[] points = {
    new Point(284, 534),
    new Point(318, 412),
    new Point(331, 388),
    new Point(357, 363),
    new Point(393, 345),
    new Point(414, 312),
    new Point(371, 247),
    new Point(338, 187),
    new Point(337, 144),
    new Point(319, 109),
    new Point(276, 128),
    new Point(240, 120),
    new Point(240, 51),
    new Point(266, 14),
    new Point(289, 0),
    new Point(598, 0),
    new Point(613, 68),
    new Point(630, 82),
    new Point(635, 110),
    new Point(608, 179),
    new Point(598, 185),
    new Point(600, 245),
    new Point(613, 252),
    new Point(657, 298),
    new Point(788, 344),
    new Point(836, 355),
    new Point(873, 364),
    new Point(873, 534),
};
Mat matMask = Mat.zeros(matSrc.size(), matSrc.type());
Imgproc.fillConvexPoly(matMask, new MatOfPoint(points), new Scalar(255, 255, 255));
displayMat(matMask, vImageMask);

Mat matBlend = Mat.zeros(matDst.size(), matDst.type());
Photo.seamlessClone(matSrc, matDst, matMask, new Point(950, 267)), matBlend, Photo.NORMAL_CLONE);
displayMat(matBlend, vImageBlend);

Android Activity

OpenCV Seamless Clone 可以找到融合方式的说明,下面另外一个融合样例中分别用了两种不同的融合方式。OpenCV seamlessClone 合成会自动处理颜色,使其看上去无缝过渡。可以看出融合后色彩不是很协调,不过可以调整区域颜色来达到更好的效果。

flag: Photo.NORMAL_CLONE

flag: Photo.MIXED_CLONE

本文使用开发环境为 Android Studio 3.2.1,OpenCV 4.0.1
本文代码仅供参考。


2018-12-26