如何用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
相关产品推荐
相关产品推荐

