如何用Python实现手写文本图像沿活页纸目标网格的变形?
手写文本图像沿活页纸弯曲网格变形实现方案
需求概述
要将神经网络生成的扁平手写文本图像,贴合到活页纸图像的弯曲网格上,分两步实现:提取活页纸的曲线网格、将文本图像按网格变形。以下是基于OpenCV+NumPy的稳定实现方案。
第一步:提取活页纸的曲线网格
实现思路
通过图像预处理提取线条,再对线条点进行曲线拟合,得到每行网格的数学表达式,最终生成网格顶点列表:
- 灰度化+二值化:突出纸张线条,过滤背景干扰
- 降噪+骨架化:提取线条的中心骨架,减少冗余点
- 曲线拟合:对每行线条的点集进行多项式拟合,得到平滑的曲线方程
代码实现
import cv2 import numpy as np from skimage.morphology import skeletonize def extract_paper_grid(paper_img_path): # 1. 读取并预处理图像 img = cv2.imread(paper_img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应二值化,突出线条 thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 降噪:开运算去除小噪点 kernel = np.ones((3,3), np.uint8) clean_thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1) # 2. 骨架化提取线条中心 skeleton = skeletonize(clean_thresh/255).astype(np.uint8)*255 # 提取线条的所有非零点 y_coords, x_coords = np.where(skeleton == 255) # 3. 按行分组并拟合曲线 grid_curves = [] # 按y坐标分组(步长根据线条间距调整,这里设为20) y_bins = np.arange(0, img.shape[0], 20) for i in range(len(y_bins)-1): mask = (y_coords >= y_bins[i]) & (y_coords < y_bins[i+1]) if np.sum(mask) < 10: continue # 获取当前行的x,y点 row_x = x_coords[mask] row_y = y_coords[mask] # 多项式拟合(3次多项式足够拟合弯曲线条) coeffs = np.polyfit(row_y, row_x, 3) grid_curves.append(coeffs) # 生成网格顶点列表(每个曲线取若干采样点) grid_points = [] sample_ys = np.linspace(0, img.shape[0], 50) # 每行取50个采样点 for coeffs in grid_curves: row_x = np.polyval(coeffs, sample_ys) row_points = list(zip(row_x.astype(int), sample_ys.astype(int))) grid_points.append(row_points) return grid_points, img.shape # 调用示例 grid_points, paper_shape = extract_paper_grid("活页纸图像路径")
第二步:将文本图像沿网格变形
实现思路
使用OpenCV的remap函数,基于网格曲线计算每个目标像素对应的源文本像素位置,实现平滑变形:
- 对齐文本与纸张的尺寸:将文本图像缩放到与活页纸图像一致的高度
- 生成变形映射表:根据网格曲线,计算每个目标(y,x)对应的源文本的(x,y)坐标
- 执行重映射:将文本图像按映射表变形,贴合网格
代码实现
def warp_text_to_grid(text_img_path, grid_points, paper_shape): # 读取文本图像并缩放至纸张高度 text_img = cv2.imread(text_img_path) text_h, text_w = text_img.shape[:2] scale = paper_shape[0] / text_h text_img_scaled = cv2.resize(text_img, (int(text_w*scale), paper_shape[0])) text_scaled_h, text_scaled_w = text_img_scaled.shape[:2] # 生成映射表:map_x[y][x] = 源图像x坐标,map_y[y][x] = 源图像y坐标 map_x = np.zeros(paper_shape, dtype=np.float32) map_y = np.zeros(paper_shape, dtype=np.float32) # 计算每行的变形比例 # 先获取网格的上下行y坐标 grid_ys = [points[0][1] for points in grid_points] # 遍历每个y坐标 for y in range(paper_shape[0]): # 找到当前y所在的网格区间 for i in range(len(grid_ys)-1): if grid_ys[i] <= y <= grid_ys[i+1]: # 获取上下两行的曲线 upper_coeffs = np.polyfit([p[1] for p in grid_points[i]], [p[0] for p in grid_points[i]], 3) lower_coeffs = np.polyfit([p[1] for p in grid_points[i+1]], [p[0] for p in grid_points[i+1]], 3) # 计算当前y对应的左右偏移量 upper_x = np.polyval(upper_coeffs, y) lower_x = np.polyval(lower_coeffs, y) # 线性插值当前行的x偏移 t = (y - grid_ys[i]) / (grid_ys[i+1] - grid_ys[i]) current_x_offset = upper_x*(1-t) + lower_x*t # 计算源文本的x坐标:按比例映射 for x in range(paper_shape[1]): # 纸张的x对应文本的x = (x - current_x_offset + text_scaled_w/2) * (text_scaled_w / paper_shape[1]) src_x = (x - current_x_offset + text_scaled_w/2) * (text_scaled_w / paper_shape[1]) src_x = np.clip(src_x, 0, text_scaled_w-1) map_x[y][x] = src_x map_y[y][x] = y # y坐标直接映射(因为文本高度已对齐) break # 执行重映射 warped_text = cv2.remap(text_img_scaled, map_x, map_y, cv2.INTER_CUBIC) # 可选:将变形后的文本与原纸张图像叠加 paper_img = cv2.imread("活页纸图像路径") result = cv2.addWeighted(paper_img, 0.5, warped_text, 0.5, 0) return warped_text, result # 调用示例 warped_text, final_result = warp_text_to_grid("手写文本图像路径", grid_points, paper_shape) cv2.imwrite("变形结果图.jpg", final_result)
关键注意事项
- 调整二值化的参数(
adaptiveThreshold的块大小和常数),适配不同亮度的活页纸图像 - 多项式拟合的阶数可根据线条弯曲程度调整(3次基本满足大多数弯曲情况)
- 若网格线条提取仍有干扰,可增加颜色过滤:先将图像转为HSV,过滤纸张的背景色后再处理
内容的提问来源于stack exchange,提问作者nekumelon
相关产品推荐
相关产品推荐

