You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python自动校正变形png_b使其与png_a物体形状一致?

解决方案:非刚性变形校正实现思路与代码

核心思路

针对非刚性挤压变形的校正,普通的仿射变换无法满足需求,需要采用基于轮廓匹配的薄板样条变换(TPS)或SimpleITK弹性配准方案,核心是建立两张图片中物体关键点的映射关系,再通过非刚性变换将变形图像对齐到目标形状。


方案一:OpenCV 轮廓匹配+TPS变换

步骤1:提取物体轮廓点

先对图片做预处理,提取目标物体的轮廓并均匀采样关键点:

import cv2
import numpy as np

def extract_object_contour(image_path):
    # 读取图片并转灰度
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 高斯去噪
    blurred = cv2.GaussianBlur(gray, (5,5), 0)
    # Otsu自动二值化
    _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    # 提取最外层轮廓(取面积最大的轮廓作为目标物体)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    max_contour = max(contours, key=cv2.contourArea)
    # 转换为(N,2)的点集并均匀采样
    points = max_contour.reshape(-1, 2).astype(np.float32)
    sample_step = max(1, len(points) // 100)
    sampled_points = points[::sample_step]
    return sampled_points, img.shape[:2]

步骤2:迭代最近点(ICP)匹配关键点

通过ICP算法找到两张图片轮廓点的对应关系:

def icp_match(source_points, target_points, max_iter=50, tolerance=1e-3):
    source = source_points.copy()
    target = target_points.copy()
    prev_error = float('inf')

    for _ in range(max_iter):
        # 计算最近邻点对
        distances = np.sqrt(((source[:, np.newaxis] - target[np.newaxis, :])**2).sum(axis=2))
        nearest_idx = np.argmin(distances, axis=1)
        matched_target = target[nearest_idx]

        # 计算刚体变换(初步对齐)
        mean_src = np.mean(source, axis=0)
        mean_tgt = np.mean(matched_target, axis=0)
        centered_src = source - mean_src
        centered_tgt = matched_target - mean_tgt

        H = centered_src.T @ centered_tgt
        U, S, Vt = np.linalg.svd(H)
        R = Vt.T @ U.T
        t = mean_tgt - R @ mean_src

        # 应用变换
        transformed_src = (R @ source.T).T + t

        # 检查收敛
        error = np.mean(np.sqrt(((transformed_src - matched_target)**2).sum(axis=1)))
        if abs(error - prev_error) < tolerance:
            break
        prev_error = error
        source = transformed_src

    return source, matched_target

步骤3:TPS非刚性变换校正图像

用OpenCV的TPS变换器将变形图像映射到目标形状:

def apply_tps_transform(image_path, src_points, tgt_points, output_shape):
    img = cv2.imread(image_path)
    # 初始化TPS变换器
    tps = cv2.createThinPlateSplineShapeTransformer()
    # 转换为OpenCV要求的格式
    src_shapes = [src_points]
    tgt_shapes = [tgt_points]
    # 估算变换关系
    tps.estimateTransformation(tgt_shapes, src_shapes)
    # 应用变换到整图
    warped_img = tps.warpImage(img, (output_shape[1], output_shape[0]))
    return warped_img

完整流程调用

# 提取两张图的轮廓点和尺寸
png_a_points, png_a_shape = extract_object_contour('png_a.png')
png_b_points, png_b_shape = extract_object_contour('png_b.png')

# 匹配关键点对
matched_b_points, matched_a_points = icp_match(png_b_points, png_a_points)

# 执行校正并保存结果
corrected_img = apply_tps_transform('png_b.png', matched_b_points, matched_a_points, png_a_shape)
cv2.imwrite('corrected_png_b.png', corrected_img)

方案二:SimpleITK弹性配准

针对复杂变形,可使用SimpleITK的BSpline弹性配准:

import SimpleITK as sitk

def sitk_non_rigid_registration(fixed_path, moving_path):
    # 读取图像并转为浮点型
    fixed = sitk.ReadImage(fixed_path, sitk.sitkFloat32)
    moving = sitk.ReadImage(moving_path, sitk.sitkFloat32)

    # 初始化配准器
    reg_method = sitk.ImageRegistrationMethod()

    # 设置互信息相似性度量
    reg_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
    reg_method.SetMetricSamplingStrategy(reg_method.RANDOM)
    reg_method.SetMetricSamplingPercentage(0.01)

    # 插值器与优化器设置
    reg_method.SetInterpolator(sitk.sitkLinear)
    reg_method.SetOptimizerAsGradientDescent(learningRate=1.0, numberOfIterations=100, convergenceMinimumValue=1e-6, convergenceWindowSize=10)
    reg_method.SetOptimizerScalesFromPhysicalShift()

    # 初始化BSpline弹性变换
    transform_mesh_size = [8] * fixed.GetDimension()
    initial_transform = sitk.BSplineTransformInitializer(fixed, transform_mesh_size)
    reg_method.SetInitialTransform(initial_transform, inPlace=False)

    # 多分辨率策略加速配准
    reg_method.SetShrinkFactorsPerLevel(shrinkFactors=[4,2,1])
    reg_method.SetSmoothingSigmasPerLevel(smoothingSigmas=[2,1,0])
    reg_method.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()

    # 执行配准并应用变换
    final_transform = reg_method.Execute(sitk.Cast(fixed, sitk.sitkFloat32), sitk.Cast(moving, sitk.sitkFloat32))
    resampler = sitk.ResampleImageFilter()
    resampler.SetReferenceImage(fixed)
    resampler.SetTransform(final_transform)
    resampler.SetInterpolator(sitk.sitkLinear)
    resampler.SetDefaultPixelValue(0)
    resampler.SetOutputPixelType(sitk.sitkUInt8)

    warped_img = resampler.Execute(moving)
    sitk.WriteImage(warped_img, 'corrected_png_b_sitk.png')
    return warped_img

注意事项

  • 若背景复杂,可替换二值化步骤为GrabCut算法分割目标物体,提升轮廓提取精度。
  • 轮廓采样点数可根据物体复杂度调整,点数过多会增加计算量,过少会降低配准精度。
  • SimpleITK配准前需确保两张图像的像素值范围一致,必要时做归一化处理。

内容的提问来源于stack exchange,提问作者Florence Thibault

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:41:18