在图像处理任务中,图片拼接是非常常见的需求:把多张小图拼成一张大图,常用于数据集还原、九宫格图片复原、图像切片后重组、拼图业务等。Pillow(PIL)通过
Image.new()创建空白画布 +paste()粘贴图片实现拼接,支持横向拼接、纵向拼接、N×N矩阵拼接。
环境准备
pipinstallpillow核心API说明
Image.new(mode, size):创建空白画布- mode:图像模式,
RGB彩色,RGBA带透明通道 - size:
(width, height)画布宽高
- mode:图像模式,
canvas.paste(image, (x, y)):把图片粘贴到画布的(x,y)坐标位置- x:粘贴起始横坐标
- y:粘贴起始纵坐标
⚠️注意:paste不会自动缩放图片,粘贴的子图尺寸必须和预留位置大小匹配。
场景1:横向拼接(左右排列)
把多张图片从左到右拼在一起,所有图片高度必须一致。
fromPILimportImagefromtypingimportListdefconcat_horizontal(img_list:List[Image.Image])->Image.Image:"""横向拼接,图片从左向右排列,要求所有图片高度相同"""ifnotimg_list:raiseValueError("图片列表不能为空")# 总宽度为所有图片宽度之和,高度取第一张图高度total_width=sum(img.widthforimginimg_list)height=img_list[0].height canvas=Image.new("RGB",(total_width,height))offset_x=0forimginimg_list:canvas.paste(img,(offset_x,0))offset_x+=img.widthreturncanvas# 示例调用if__name__=="__main__":im1=Image.open("a.jpg")im2=Image.open("b.jpg")im3=Image.open("c.jpg")result=concat_horizontal([im1,im2,im3])result.save("horizontal.jpg")场景2:纵向拼接(上下排列)
多张图片从上往下拼接,所有图片宽度必须一致。
defconcat_vertical(img_list:List[Image.Image])->Image.Image:"""纵向拼接,图片从上向下排列,要求所有图片宽度相同"""ifnotimg_list:raiseValueError("图片列表不能为空")total_height=sum(img.heightforimginimg_list)width=img_list[0].width canvas=Image.new("RGB",(width,total_height))offset_y=0forimginimg_list:canvas.paste(img,(0,offset_y))offset_y+=img.heightreturncanvas# result = concat_vertical([im1, im2, im3])# result.save("vertical.jpg")场景3:N×N矩阵拼接(和之前分割函数配套)
针对上一篇博客split_image()分割出来的子图列表,行优先还原大图。输入子图列表 + 矩阵阶数split_count,输出完整大图。
适用:3×3、4×4九宫格复原,子图尺寸全部相同。
defmerge_image_grid(tile_list:List[Image.Image],split_count:int)->Image.Image:""" N × N 矩阵拼接,与split_image分割函数配套 :param tile_list: split_image返回的子图列表,行优先顺序 :param split_count: 分割份数,3代表3行3列 :return: 拼接完成的PIL Image对象 """ifnottile_list:raiseValueError("子图列表不能为空")iflen(tile_list)!=split_count*split_count:raiseValueError(f"子图数量不匹配,需要{split_count*split_count}张,实际{len(tile_list)}张")tile_w,tile_h=tile_list[0].size canvas_w=tile_w*split_count canvas_h=tile_h*split_count canvas=Image.new("RGB",(canvas_w,canvas_h))index=0foryinrange(split_count):forxinrange(split_count):canvas.paste(tile_list[index],(x*tile_w,y*tile_h))index+=1returncanvasif__name__=="__main__":# 和分割代码完整联调示例fromPILimportImagefromyour_moduleimportsplit_image big_img=Image.open("300x300.jpg")tiles=split_image(big_img,split_count=3)merged_img=merge_image_grid(tiles,split_count=3)merged_img.save("merge_output.jpg")场景4:自定义行列拼接(M行N列,非正方形)
如果你想要 2行3列,不是N×N正方形矩阵:
defmerge_custom_grid(tile_list:List[Image.Image],rows:int,cols:int)->Image.Image:"""自定义行列矩阵拼接 rows行 cols列"""tile_w,tile_h=tile_list[0].size canvas=Image.new("RGB",(tile_w*cols,tile_h*rows))idx=0foryinrange(rows):forxinrange(cols):canvas.paste(tile_list[idx],(x*tile_w,y*tile_h))idx+=1returncanvas# 示例:2行3列,共6张图片# out = merge_custom_grid(tiles, rows=2, cols=3)处理透明通道 RGBA
如果你的图片是png带透明,创建画布时把模式改为RGBA:
canvas=Image.new("RGBA",(canvas_w,canvas_h))⚠️常见踩坑点
- 子图尺寸不一致:paste不会自动缩放,子图大小不一样会出现错位、溢出。需要先统一尺寸。
- 图片模式不匹配:RGB图片粘贴到RGBA画布可以;RGBA粘贴到RGB画布会丢失透明通道,建议统一模式。
- 列表顺序错误:矩阵拼接严格依赖列表顺序,必须和分割时遍历顺序(行优先)保持一致。
- 索引越界:子图数量不等于 rows×cols,直接抛出异常,避免生成错误图片。
完整闭环:分割 + 拼接完整示例
fromPILimportImageimportosfromtypingimportListdefsplit_image(img:Image.Image,split_count:int,save_dir:str=None)->List[Image.Image]:w,h=img.size tile_w=w//split_count tile_h=h//split_count tile_list:List[Image.Image]=[]foryinrange(split_count):forxinrange(split_count):left=x*tile_w upper=y*tile_h right=left+tile_w lower=upper+tile_h tile=img.crop((left,upper,right,lower))tile_list.append(tile)ifsave_dirisnotNone:os.makedirs(save_dir,exist_ok=True)tile.save(os.path.join(save_dir,f"tile_x{x}_y{y}.png"))returntile_listdefmerge_image_grid(tile_list:List[Image.Image],split_count:int)->Image.Image:ifnottile_list:raiseValueError("子图列表不能为空")iflen(tile_list)!=split_count*split_count:raiseValueError(f"子图数量不匹配,需要{split_count*split_count}张,实际{len(tile_list)}张")tile_w,tile_h=tile_list[0].size canvas_w=tile_w*split_count canvas_h=tile_h*split_count canvas=Image.new("RGB",(canvas_w,canvas_h))index=0foryinrange(split_count):forxinrange(split_count):canvas.paste(tile_list[index],(x*tile_w,y*tile_h))index+=1returncanvasif__name__=="__main__":src_img=Image.open("test.jpg")tile_list=split_image(src_img,split_count=3)print(f"分割得到{len(tile_list)}张图片")restore_img=merge_image_grid(tile_list,split_count=3)restore_img.save("restore.jpg")print("完成分割复原,输出 restore.jpg")总结
Image.new()创建空白画布,paste()实现图片粘贴,是Pillow拼接的底层核心。- 分为横向、纵向、矩阵N×N三种常用场景;矩阵拼接可以完美复原之前分割出来的图片。
- 注意图片尺寸、图像模式、列表顺序,这三个是拼接出错最高的地方。
- 分割与拼接组合,完成图片切片、复原完整流程,适合数据集预处理、九宫格业务。
拓展方向:可以增加边框、间隙,在子图之间添加空白间隔;也可以增加缩放逻辑自动适配子图大小。