如何将带透明背景的不规则PNG图像扭曲为圆形?
问题:将不规则透明PNG扭曲为圆形失败
我想把带透明背景的不规则PNG图像扭曲成圆形,后续要投影到半球外侧,所以先拉伸填充圆形是合理步骤。我的计划步骤是:
- 将图像调整为正方形
- 获取图像轮廓转为np数组
- 创建与图像直径相同、点数和轮廓一致的圆形
- 用
findHomography获取变换矩阵 - 用矩阵扭曲源图像
原实现代码:
def image_to_circle(filename: str): source_image = cv2.imread(filename) source_gray = cv2.cvtColor(source_image, cv2.COLOR_BGR2GRAY) # resize image and grayscale image to a square height, width = source_gray.shape dimension = min(width, height) source_image = cv2.resize(source_image, (dimension, dimension)) source_gray = cv2.resize(source_gray, (dimension, dimension)) # find the source contour contours, _ = cv2.findContours( source_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) source_contour = max(contours, key=cv2.contourArea) radius = dimension / 2 target_contour = generate_circle_points(radius, len(source_contour)) # compute the perspective transformation matrix homography_matrix, _ = cv2.findHomography(source_contour, target_contour) # warp the source image to the destination based on the homography matrix warped_image = cv2.warpPerspective( source_image, homography_matrix, (dimension, dimension) ) cv2.imwrite("warped-" + filename, warped_image)
def generate_circle_points(radius, num_points): angles = np.linspace(0, 2 * np.pi, num_points) x = radius * np.cos(angles) y = radius * np.sin(angles) return np.column_stack((x, y))
但该方案无法正常工作,输入房屋图像后输出结果不符合预期,怀疑是warpPerspective的工作方式和预期不符。
问题分析
- 单应性变换的局限性:
findHomography实现的单应性变换是平面到平面的线性投影变换,只能处理平行、透视这类平面变形,无法完成不规则形状到圆形的非线性扭曲——圆形是曲线边界,单应性做不到这种非线性拉伸。 - 轮廓点匹配错误:原代码直接将轮廓点与圆形点按顺序一一对应,但两者的点排列顺序(顺时针/逆时针、起始点)未必一致,会导致变换错乱。
- 坐标原点偏移问题:生成的圆形点以(0,0)为中心,但图像坐标原点在左上角,未做偏移处理,导致圆形位置完全错误。
- 透明通道丢失:
cv2.imread默认会忽略PNG的透明通道,处理后图像丢失透明信息,不符合后续投影需求。
修正方案:非线性径向映射
改用**径向映射(极坐标变换)**实现非线性扭曲,同时保留透明通道,步骤如下:
- 读取PNG时保留透明通道
- 将图像调整为正方形
- 生成极坐标网格,把正方形内的点按径向比例映射到圆形区域
- 用双线性插值完成重映射
- 添加圆形掩码,将圆外区域设为透明
修正后的代码:
import cv2 import numpy as np def image_to_circle(filename: str): # 读取PNG并保留透明通道 source_image = cv2.imread(filename, cv2.IMREAD_UNCHANGED) if source_image is None: raise ValueError(f"无法读取图像文件: {filename}") # 调整为正方形 height, width = source_image.shape[:2] dimension = min(width, height) source_image = cv2.resize(source_image, (dimension, dimension)) # 定义圆形的中心和半径 radius = dimension / 2 center = (radius, radius) # 生成目标图像的网格坐标 y, x = np.mgrid[0:dimension, 0:dimension].astype(np.float32) # 转换为相对于中心的极坐标 dx = x - center[0] dy = y - center[1] r = np.sqrt(dx**2 + dy**2) theta = np.arctan2(dy, dx) # 计算原图像的对应坐标:径向比例映射 max_r = radius scale = r / max_r # 超出圆形范围的点映射到圆边界 scale[r > max_r] = 1.0 src_x = center[0] + dx / scale src_y = center[1] + dy / scale # 确保坐标在原图像范围内,避免越界 src_x = np.clip(src_x, 0, dimension - 1) src_y = np.clip(src_y, 0, dimension - 1) # 双线性插值重映射 warped_image = cv2.remap(source_image, src_x, src_y, cv2.INTER_LINEAR) # 生成圆形掩码,设置圆外区域为透明 mask = np.zeros((dimension, dimension), dtype=np.uint8) cv2.circle(mask, (int(center[0]), int(center[1])), int(radius), 255, -1) warped_image[..., 3] = mask # 应用到透明通道 cv2.imwrite("warped-" + filename, warped_image) if __name__ == "__main__": image_to_circle("house.png")
内容的提问来源于stack exchange,提问作者Raven
相关产品推荐
相关产品推荐

