Poisson图像编辑
onlyloveyd 173 4

泊松图像编辑

图像合成是图形处理的一个基本问题,其通过将源图像中一个物体或者一个区域嵌入到目标图像生成一个新的图像。在对图像进行合成的过程中,为了使合成后的图像更自然,合成边界应保持无缝。但如果原图像和目标图像有着明显不同的纹理特征,则直接合成后的图像会存在明显的边界。针对此问题,Prez等提出了一种利用构造Poisson方程求解像素最优值的方法,在保留了源图像梯度信息的同时,可以很好地融合源图像与目标图像的背景。该方法根据用户指定的边界条件求解一个Poisson方程,实现了梯度上的连续,从而达到边界处的无缝融合。Poisson图像编辑的主要思想是,根据原图像的梯度信息以及目标图像的边界信息,利用插值的方法重新构建出和成区域内的图像像素。

原理

论文:Patrick Pérez, Michel Gangnet, and Andrew Blake. Poisson image editing. In ACM Transactions on Graphics (TOG), volume 22, pages 313–318. ACM, 2003.

论文通俗解释:https://blog.csdn.net/hjimce/article/details/45716603

API

无缝克隆

public static void seamlessClone(Mat src, Mat dst, Mat mask, Point p, Mat blend, int flags)
  • 参数一:src,输入图像,8位三通道。
  • 参数二:dst,输入图像,8位三通道。
  • 参数三:mask,输入图像,8位单通道或者8位三通道。
  • 参数四:p,克隆图像在dst图像上的中心位置。
  • 参数五:blend, 输出图像。大小类型与dst相同。
  • 参数六:flags,克隆方式标志位。
public static final int
        ……
        NORMAL_CLONE = 1,        // 把待克隆的src对象完整的插入到dst目标图像图像中去,不改变其轮廓特征与结构
        MIXED_CLONE = 2,         // 混合克隆跟正常克隆相比,它会把背景颜色与纹理考虑进去,对轮廓特征与背景实现透明通道混合。
        MONOCHROME_TRANSFER = 3, // 基于特征的迁移融合,只会把特征融合到背景图像当中。

三幅图合为一幅图,src与mask抠图(逻辑与,尺寸一致),把抠出的图融合到dst中的p位置处(抠出的图尺寸≤dst图)。p位置也是抠出的图的中心。

调整颜色

public static void colorChange(Mat src, Mat mask, Mat dst, float red_mul, float green_mul, float blue_mul)
  • 参数一:src,输入图像,8位三通道。
  • 参数二:mask,输入图像,8位单通道或者8位三通道。
  • 参数三:dst,输出图像,大小类型与src相同。
  • 参数四:red_mul,R通道倍增系数。
  • 参数五:green_mul,G通道倍增系数。
  • 参数六:blue_mul,B通道倍增系数。

对感兴趣区域进行颜色调整。

消除高亮

public static void illuminationChange(Mat src, Mat mask, Mat dst, float alpha, float beta)
  • 参数一:src,输入图像,8位三通道。
  • 参数二:mask,输入图像,8位单通道或者8位三通道。
  • 参数三:dst,输出图像,大小类型与src相同。
  • 参数四:alpha,0-2。
  • 参数五:beta,0-2。

消除高亮区域,alpha,beta两个参数共同决定消除高光后图像的模糊程度(范围0~2,0比较清晰,2比较模糊)。

纹理扁平化

public static void textureFlattening(Mat src, Mat mask, Mat dst, float low_threshold, float high_threshold, int kernel_size) 
  • 参数一:src,输入图像,8位三通道。
  • 参数二:mask,输入图像,8位单通道或者8位三通道。
  • 参数三:dst,输出图像,大小类型与src相同。
  • 参数四:low_threshold,0-100的低阈值。
  • 参数五:high_threshold,大于100的高阈值。
  • 参数六:kernel_size,Sobel核的大小。

纹理扁平化,边缘检测器选取的边缘越少(选择性越强),边缘映射就越稀疏,扁平化效果就越明显。该算法假定源图像的颜色接近目标图像的颜色。这种假设意味着,当颜色不匹配时,源图像的颜色将趋向于目标图像的颜色。

操作

/**
 * 泊松图像编辑
 * author: yidong
 * 2020/12/11
 */
class PoissonImageEditActivity : AppCompatActivity() {

    private val mList = mutableListOf<ImageTextObject>()
    private lateinit var mAdapter: ImageTextAdapter

    private val mBinding: ActivityPoissonImageEditBinding by lazy {
        ActivityPoissonImageEditBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)

        mAdapter = ImageTextAdapter(this, mList)
        mBinding.container.adapter = mAdapter
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_cloning, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        title = item.title
        when (item.itemId) {
            R.id.normal_cloning -> {
                this.wrapCoroutine({ showLoading() }, { doNormalCloning() }, { hideLoading() })
            }
            R.id.mixed_cloning -> {
                this.wrapCoroutine({ showLoading() }, { doMixedCloning() }, { hideLoading() })
            }
            R.id.monochrome_transfer -> {
                this.wrapCoroutine({ showLoading() }, { doMonochromeTransfer() }, { hideLoading() })
            }
            R.id.local_color_change -> {
                this.wrapCoroutine({ showLoading() }, { doColorChange() }, { hideLoading() })
            }
            R.id.local_illumination_change -> {
                this.wrapCoroutine({ showLoading() }, { doIlluminationChange() }, { hideLoading() })
            }
            R.id.texture_flattening -> {
                this.wrapCoroutine({ showLoading() }, { doTextureFlattening() }, { hideLoading() })
            }
        }
        return true
    }

    private fun doNormalCloning() {
        val source = Utils.loadResource(this, R.drawable.normal_cloning_source)
        val mask = Utils.loadResource(this, R.drawable.normal_cloning_mask)
        val destination = Utils.loadResource(this, R.drawable.normal_cloning_destination)
        val result = Mat()
        val center = Point(400.toDouble(), 100.toDouble())
        Photo.seamlessClone(source, destination, mask, center, result, Photo.NORMAL_CLONE)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(destination.toRgb(), "destination"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun doMixedCloning() {
        val source = Utils.loadResource(this, R.drawable.mixed_cloning_source)
        val mask = Utils.loadResource(this, R.drawable.mixed_cloning_mask)
        val destination = Utils.loadResource(this, R.drawable.mixed_cloning_destination)
        val result = Mat()
        val center = Point(destination.size().width / 2.0, destination.size().height / 2.0)
        Photo.seamlessClone(source, destination, mask, center, result, Photo.MIXED_CLONE)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(destination.toRgb(), "destination"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun doMonochromeTransfer() {
        val source = Utils.loadResource(this, R.drawable.monochrome_transfer_source)
        val mask = Utils.loadResource(this, R.drawable.monochrome_transfer_mask)
        val destination = Utils.loadResource(this, R.drawable.monochrome_transfer_destination)
        val result = Mat()
        val center = Point(destination.size().width / 2.0, destination.size().height / 2.0)
        Photo.seamlessClone(source, destination, mask, center, result, Photo.MONOCHROME_TRANSFER)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(destination.toRgb(), "destination"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun doColorChange() {
        val source = Utils.loadResource(this, R.drawable.color_change_source)
        val mask = Utils.loadResource(this, R.drawable.color_change_mask)
        val result = Mat()
        Photo.colorChange(source, mask, result, 1.5F, .5F, .5F)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun doIlluminationChange() {
        val source = Utils.loadResource(this, R.drawable.illumination_change_source)
        val mask = Utils.loadResource(this, R.drawable.illumination_change_mask)
        val result = Mat()
        Photo.illuminationChange(source, mask, result, 0.2f, 0.4f)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun doTextureFlattening() {
        val source = Utils.loadResource(this, R.drawable.texture_flattening_source)
        val mask = Utils.loadResource(this, R.drawable.texture_flattening_mask)
        val result = Mat()
        Photo.textureFlattening(source, mask, result, 30F, 45F, 3)

        mList.clear()
        mList.add(ImageTextObject(source.toRgb(), "source"))
        mList.add(ImageTextObject(mask, "mask"))
        mList.add(ImageTextObject(result.toRgb(), "result"))
    }

    private fun showLoading() {
        mBinding.isLoading = true
    }

    private fun hideLoading() {
        mAdapter.notifyDataSetChanged()
        mBinding.isLoading = false
    }
}

效果

Poisson图像编辑

Poisson图像编辑

Poisson图像编辑

Poisson图像编辑

Poisson图像编辑

Poisson图像编辑

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

评论区

索引目录