Kotlin Bytedeco OpenCV 图像图像58 无缝克隆

Kotlin Bytedeco OpenCV 图像图像58 无缝克隆

0 原理解释

OpenCV 的 cv2.seamlessClone() 就是实现泊松图像编辑(Poisson Image Editing) ,核心目标:把一张小图(前景源图)粘贴到目标背景图上,消除拼接边界,融合过渡自然,看不到硬缝合线

普通拷贝:直接把源图像素覆盖到背景,边界会有明显一圈硬边、色彩断层。

无缝克隆:不直接拷贝像素颜色,拷贝的是图像梯度(变化),求解泊松方程重建像素值。

核心思想

图像可以看作二维函数 (f(x,y))

  • 梯度 ∇f:像素灰度/颜色的变化(边缘、纹理、明暗变化)
  • 泊松编辑:在粘贴的ROI区域内部,尽量保留源图像的梯度纹理;在区域边界,强制和背景图像素保持一致

通俗一句话:内部用源图的纹理变化,边界接上背景图的颜色,求解出区域内每个点最终的像素值

两个关键输入

  1. 源图 src:要贴过去的物体
  2. 掩码 mask:标记哪些像素是要粘贴的物体(白色=前景,黑色忽略)
  3. 目标图 dst:背景大图
  4. 中心点:源物体放在背景图上的中心坐标

数学原理(泊松方程)

在粘贴区域Ω内求解泊松偏微分方程:

\\Delta f = \\Delta f\^\* \\quad \\text{区域}\\ \\Omega \\text{内部}

f = f_{background} \\quad \\text{在区域边界}\\ \\partial\\Omega

  • (\Delta):拉普拉斯算子,本质就是梯度的散度,代表图像二阶差分(纹理变化)
  • (f^):源图像;(\Delta f^) 就是源图的拉普拉斯(源图纹理信息)
  • 边界条件 Dirichlet:粘贴区域的边缘像素,直接等于背景图像素

含义拆解:

  1. 在粘贴的物体内部:让合成图像的拉普拉斯 = 源图像拉普拉斯 → 保留源物体纹理、明暗、细节
  2. 在物体的轮廓边界:像素值强制等于背景,消除硬边界断层

把连续微分方程离散化,变成大型稀疏线性方程组 Ax=b,解方程组得到每个点新的R/G/B像素(三通道分别求解),得到融合后的图片。

OpenCV内部就是求解这套稀疏线性方程组,这就是无缝克隆的计算本质。

seamlessClone 的几种模式

python 复制代码
cv2.seamlessClone(src, dst, mask, center, flags)
  • cv2.NORMAL_CLONE 普通泊松克隆(最常用):上面标准泊松方程,复制源梯度,边界贴合背景
  • cv2.MIXED_CLONE 混合克隆:源和背景梯度做混合;物体和背景光照差异大时效果更好
  • cv2.MONOCHROME_TRANSFER:只迁移纹理梯度,丢弃源图颜色;只把纹理贴到背景上。

优缺点

✅ 优点:

  1. 物体轮廓边界过渡自然,消除硬拼接缝
  2. 物体内部纹理细节完整保留
  3. 自动适配背景的亮度、色调

❌ 缺点:

  1. 非常依赖mask掩码质量:mask轮廓不准,融合就会有伪影、晕边;掩码要稍微比物体轮廓大一圈
  2. 大面积区域求解线性方程组,大mask会慢
  3. 如果源图和背景光照差异极端,会出现色彩偏移、发灰;此时改用 MIXED_CLONE
  4. 只融合边界附近,物体本身大的颜色差异无法完全修复

和普通直接copy对比

  • 直接拷贝:dst[roi] = src[roi] → 复制像素值,边界生硬
  • 泊松无缝克隆:复制梯度(纹理),边界约束背景像素,解方程重建颜色。不是简单像素拷贝。

简单伪代码逻辑

  1. 根据mask得到待融合区域Ω
  2. 对每个颜色通道R/G/B,构建稀疏线性方程组
    • Ω内部:约束等于源图拉普拉斯
    • Ω边界:约束等于背景图像素
  3. 求解方程组得到每个点新像素
  4. 把解出来的像素回填到背景图对应位置输出

常见踩坑

  1. mask必须是和src一样尺寸的单通道图,前景物体是白色(255),不是作用在dst上;很多人mask尺寸搞错导致完全不对。
  2. mask轮廓尽量紧贴物体,可以稍微外扩1~2像素,不要留有黑边缝隙。
  3. center坐标是目标图上的中心点,不是左上角。

1 添加依赖

1.1 build.gradle.kts

kotlin 复制代码
plugins {
    kotlin("jvm") version "2.4.0"
    application
}

group = "com.xuhyac"
version = "1.0.0"

repositories {
    maven("https://maven.aliyun.com/repository/public")
    mavenCentral()
}

dependencies {
    implementation("org.bytedeco:opencv:4.13.0-1.5.13")
    runtimeOnly("org.bytedeco:opencv:4.13.0-1.5.13:windows-x86_64")
    runtimeOnly("org.bytedeco:openblas:0.3.31-1.5.13:windows-x86_64")
    testImplementation(kotlin("test"))
}

kotlin {
    jvmToolchain(25)
}

application {
    mainClass.set(providers.gradleProperty("mainClass").orElse("com.xuhyac.MainKt"))
    applicationDefaultJvmArgs = listOf(
        "--enable-native-access=ALL-UNNAMED",
        "-Dstdout.encoding=UTF-8",
        "-Dstderr.encoding=UTF-8",
    )
}

tasks.test {
    useJUnitPlatform()
}

1.2 settings.gradle.kts

kotlin 复制代码
pluginManagement {
    repositories {
        maven("https://maven.aliyun.com/repository/gradle-plugin")
        maven("https://maven.aliyun.com/repository/public")
        gradlePluginPortal()
        mavenCentral()
    }
}

plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "opencv"

2 测试代码

kotlin 复制代码
package com.xuhyac.photo

import com.xuhyac.common.DemoSupport.materializeResource
import org.bytedeco.opencv.global.opencv_core.CV_8UC1
import org.bytedeco.opencv.global.opencv_core.CV_8UC3
import org.bytedeco.opencv.global.opencv_highgui
import org.bytedeco.opencv.global.opencv_imgcodecs.IMREAD_COLOR
import org.bytedeco.opencv.global.opencv_imgcodecs.imread
import org.bytedeco.opencv.global.opencv_imgproc.*
import org.bytedeco.opencv.global.opencv_photo.NORMAL_CLONE
import org.bytedeco.opencv.global.opencv_photo.seamlessClone
import org.bytedeco.opencv.opencv_core.Mat
import org.bytedeco.opencv.opencv_core.Point
import org.bytedeco.opencv.opencv_core.Scalar
import kotlin.io.path.absolutePathString

/**
 * 使用泊松图像编辑把前景内容无缝克隆到另一张图中。
 *
 * 普通 Alpha 混合直接组合像素值,前景与背景亮度不一致时容易出现明显边界。`seamlessClone` 在掩膜区域
 * 内求解泊松方程,尽量保留源图的梯度,同时让边界颜色服从目标图,因此能自然适配目标环境的光照。
 * `NORMAL_CLONE` 保留源图整体梯度;需要更多目标纹理时可以尝试 `MIXED_CLONE`。
 *
 * 示例程序生成一个带文字和环形边缘的前景,用圆形掩膜将其克隆到 OpenCV 官方篮球图片中央。源图必须
 * 与掩膜同尺寸,克隆中心还应确保整个源图范围不会超出目标图。
 *
 */
fun main() {
    val path = materializeResource("opencv/images/basketball1.png")
    val destination = imread(path.absolutePathString(), IMREAD_COLOR)
    val sourceSize = 180
    val source = Mat(sourceSize, sourceSize, CV_8UC3, Scalar(35.0, 150.0, 235.0, 0.0))
    val mask = Mat.zeros(sourceSize, sourceSize, CV_8UC1).asMat()
    val center = Point(sourceSize / 2, sourceSize / 2)

    circle(source, center, 76, Scalar(230.0, 80.0, 35.0, 0.0), 12, LINE_AA, 0)
    circle(source, center, 58, Scalar(50.0, 205.0, 90.0, 0.0), -1, LINE_AA, 0)
    putText(source, "CV", Point(42, 108), FONT_HERSHEY_DUPLEX, 1.8, Scalar(245.0, 245.0, 245.0, 0.0), 4, LINE_AA, false)
    circle(mask, center, 83, Scalar(255.0, 255.0, 255.0, 0.0), -1, LINE_AA, 0)

    val cloneCenter = Point(destination.cols() / 2, destination.rows() / 2)
    val blended = Mat()
    seamlessClone(source, destination, mask, cloneCenter, blended, NORMAL_CLONE)

    opencv_highgui.imshow("src", source)
    opencv_highgui.imshow("mask", mask)
    opencv_highgui.imshow("destination", destination)
    opencv_highgui.imshow("blended", blended)
    opencv_highgui.waitKey(0)
    println("已在目标坐标 (${cloneCenter.x()}, ${cloneCenter.y()}) 完成 NORMAL_CLONE")
}

3 测试结果

相关推荐
朝阳391 小时前
react19【系列实用教程】实用组件封装
开发语言·前端·javascript
我是苏苏1 小时前
C#基础:使用System.Speech.Synthesis离线播放/朗读文字内容
开发语言·c#
Patrick在香港2 小时前
Python依赖管理从踩坑到选型:pip/poetry/uv四种方案全面实测
开发语言·python·数据分析·scikit-learn·ai编程·pip·uv
我命由我123452 小时前
Kotlin 面向对象 - 枚举排序
android·java·开发语言·java-ee·kotlin·android studio·android-studio
程序喵大人2 小时前
【C++进阶】STL容器与迭代器 - 10 把容器、迭代器和数据流串成一个小程序
开发语言·c++·容器·小程序·迭代器·stl
luj_17682 小时前
随机性在算法与占卜中的共通原理
c语言·开发语言·c++·经验分享·算法
Lazionr2 小时前
vector初识——从动态数组到STL核心容器
开发语言·c++
AI视觉网奇2 小时前
glb读取颜色
开发语言·python
程序员-Benothing3 小时前
如何实现高并发系统订单30分钟自动关闭?
开发语言·c#·wpf