资讯动态

PNG 的“透明背景”是真的吗?用 Pillow 和 ImageMagick 检查 Alpha 通道(附批量脚本)

发布时间:2026/10/26 10:27:46 来源:尧图企业网站定制
做海报时经常遇到这种情况下载来的素材文件名写着“透明底”后缀也是.png可一放到深色底上就露馅了有一张背后露出一整片灰白格子有一张边缘绕着一圈白边还有一张打开后背景是纯白但图层面板里显示“有透明通道”。这类问题靠肉眼很难一次判断准。原因是很多看图软件会用棋盘格来表示透明区域而有些素材恰好把棋盘格画进了像素里两者在屏幕上几乎看不出区别。比较稳的办法是直接读文件里的数据。下面是我整理的检查流程从“不装任何库”到“批量出报告”分三档命令都在本机实际跑过。测试环境Python 3.13 Pillow 12.3ImageMagick 7.1.2。文中的示例图是用脚本生成的 256×256 测试图方便复现。一、先弄清楚PNG 里的透明是怎么存的PNG 的文件头里有一个 IHDR 块其中“颜色类型”color type这个字段决定了像素的存储方式颜色类型含义能不能表示透明0灰度只能靠 tRNS 块指定“某一个灰度值算全透明”2真彩色 RGB只能靠 tRNS 块指定“某一个颜色算全透明”3索引色调色板tRNS 块可以给调色板里的每种颜色单独设透明度4灰度 Alpha每个像素都有独立的 Alpha 值6真彩色 AlphaRGBA每个像素都有独立的 Alpha 值几个要点8 位 Alpha 的取值是 0–2550 表示完全透明255 表示完全不透明中间的值就是半透明。阴影、玻璃、发丝这类边缘都要靠中间值。透明信息有两条存储路径一条是颜色类型 4/6 自带的 Alpha 通道另一条是 tRNS 辅助块。只检查“是不是 RGBA”会漏掉调色板 PNG 的情况。其他格式顺带说一句JPEG 不支持透明GIF 只能把调色板里的一个颜色设成全透明没有半透明WebP有损和无损都可以和 AVIF 支持 Alpha。二、零依赖直接读文件头颜色类型在 PNG 文件里的位置是固定的8 字节文件签名加 IHDR 的长度4 字节和类型名4 字节再加宽、高各 4 字节、位深 1 字节所以颜色类型落在第 25 字节从 0 开始数。Linux/macOS 自带的od就能读od-An-tu1-j25-N1real_alpha.png# 输出 6表示 RGBA这一步只能看到“有没有 Alpha 通道”看不到 tRNS也识别不了改过后缀的文件。所以我又写了一个只用标准库的小脚本把所有块按顺序列出来# png_chunks.py —— 只用标准库列出 PNG 的颜色类型和数据块importstruct,sys COLOR_TYPES{0:灰度,2:RGB,3:调色板,4:灰度Alpha,6:RGBA}defpng_chunks(path):withopen(path,rb)asf:iff.read(8)!b\x89PNG\r\n\x1a\n:raiseValueError(文件头不是 PNG可能是改了后缀的 JPG/WebP)whileTrue:headf.read(8)iflen(head)8:returnlength,namestruct.unpack(I4s,head)dataf.read(length)f.read(4)# 跳过 CRCyieldname.decode(latin-1),dataifnamebIEND:returnforpathinsys.argv[1:]:try:chunkslist(png_chunks(path))exceptValueErrorase:print(f{path}:{e})continuew,h,depth,ctypestruct.unpack(IIBB,chunks[0][1][:10])names[nforn,_inchunks]print(f{path}:{w}x{h}位深{depth}颜色类型{ctype}({COLOR_TYPES.get(ctype,?)})f tRNS{有iftRNSinnameselse无}块:{ .join(dict.fromkeys(names))})运行结果节选$ python3 png_chunks.py *.png banner.png: 文件头不是 PNG可能是改了后缀的 JPG/WebP fake_checker.png: 256x256 位深8 颜色类型2(RGB) tRNS无 块: IHDR IDAT IEND palette_trns.png: 256x256 位深8 颜色类型3(调色板) tRNS有 块: IHDR PLTE tRNS IDAT IEND real_alpha.png: 256x256 位深8 颜色类型6(RGBA) tRNS无 块: IHDR IDAT IENDbanner.png其实是一张 JPEG只是被改了后缀这种文件在素材包里并不少见。三、Pillow统计 Alpha 分布判断“透得对不对”“有 Alpha 通道”和“真的透明”是两回事。很多导出工具会给每张图都写上 Alpha 通道但所有像素的值都是 255。下面这个脚本做了四件事把调色板/tRNS 透明统一转换成 RGBA 再分析统计全透明、半透明、不透明像素各自的占比没有透明信息时粗略判断左上角是不是画上去的棋盘格对比半透明边缘和主体的亮度提示可能存在的白边。# check_alpha.py —— 用法python3 check_alpha.py 素材目录/*.pngimportsysfrompathlibimportPathfromPILimportImage,ImageStatdeflooks_like_checkerboard(im,box32):粗略判断左上角是不是画上去的灰白棋盘格启发式仅供参考gim.convert(L).crop((0,0,box,box))histg.histogram()# 每 16 级灰度合成一档抗一点压缩噪声bins[sum(hist[i:i16])foriinrange(0,256,16)]topsorted(range(16),keylambdai:bins[i],reverseTrue)[:2]covered(bins[top[0]]bins[top[1]])/(box*box)returncovered0.9andmin(top)10andbins[top[1]]box*box*0.2defcheck(path):imImage.open(path)modeim.modeifmodePor(modein(L,RGB)andtransparencyinim.info):imim.convert(RGBA)# 调色板 / tRNS 透明先展开成 RGBAifAnotinim.getbands():hint左上角疑似棋盘格假透明iflooks_like_checkerboard(im)elsereturnf{mode:5}无透明信息{hint}aim.getchannel(A)hista.histogram()totalim.width*im.height clear,solidhist[0],hist[255]partialtotal-clear-solidifclearpartial0:returnf{mode:5}有 Alpha 通道但全部不透明形同 RGBedge有半透明过渡ifpartialelse只有 0/255硬边ifpartialandsolid:lumaim.convert(L)edge_maska.point(lambdav:255if0v255else0)solid_maska.point(lambdav:255ifv255else0)diff(ImageStat.Stat(luma,edge_mask).mean[0]-ImageStat.Stat(luma,solid_mask).mean[0])ifdiff40:edgef边缘比主体亮{diff:.0f}疑似白边return(f{mode:5}透明{clear/total:.1%}半透明{partial/total:.1%}f不透明{solid/total:.1%}→{edge})if__name____main__:forpinsys.argv[1:]:print(f{Path(p).name:20}{check(p)})对 6 张测试图的输出fake_checker.png RGB 无透明信息左上角疑似棋盘格假透明 halo.png RGBA 透明 60.5% 半透明 16.1% 不透明 23.4% → 有半透明过渡边缘比主体亮 72疑似白边 hard_edge.png RGBA 透明 69.0% 半透明 0.0% 不透明 31.0% → 只有 0/255硬边 palette_trns.png P 透明 74.6% 半透明 0.0% 不透明 25.4% → 只有 0/255硬边 real_alpha.png RGBA 透明 60.5% 半透明 16.1% 不透明 23.4% → 有半透明过渡 rgba_opaque.png RGBA 有 Alpha 通道但全部不透明形同 RGB怎么读这份结果无透明信息 疑似棋盘格这是“假透明”。棋盘格是像素的一部分放到任何背景上都会跟着出现。全部不透明文件格式没问题但背景并没有被去掉。打开后看到白底就是因为背景像素本来就是白色。只有 0/255 的硬边做图标、像素风素材没问题。但如果主体是人像、毛绒或者带投影放大后边缘会有锯齿。疑似白边半透明像素的颜色混进了原来的白底。贴在浅色背景上看不出来一放到深色背景上就会出现一圈亮边。阈值40、0.9 等是我按自己的素材调出来的经验值不是标准。如果主体本身就是白色或浅色白边检测会失灵如果主体本身就是黑白格纹样棋盘格检测也会误报。这两项只能作为“需要人工复看”的提示。顺带提一下Pillow 10.1 起有im.has_transparency_data属性。它只说明文件里带有透明数据上面那张全不透明的rgba_opaque.png也返回True所以不能拿它来代替统计。四、ImageMagick命令行快速排查不想写 Python 的话ImageMagick 的几条命令也够用。以下命令在 7.x 上测过6.x 一般把magick换成convert/identify即可个别输出格式可能有差别。1. 一次看格式、通道和是否全不透明magick identify-format%f %m %[channels] opaque%[opaque]\n*.png输出节选banner.png JPEG srgb 3.0 opaqueTrue real_alpha.png PNG srgba 4.0 opaqueFalse rgba_opaque.png PNG srgba 4.0 opaqueTrue%m显示的是真实格式改过后缀的文件在这里会直接现形。2. 批量挑出“其实不透明”的文件magick identify-format%f %[opaque]\n*.png|grepTrue3. 看 Alpha 的分布# Alpha 只有 0 和 255 两个值时直方图只有两行magick hard_edge.png-alphaextract-format%c histogram:info:-# 半透明像素占比-fx 逐像素计算大图会慢magick real_alpha.png-alphaextract-fxu0u1-format%[fx:mean]\ninfo:4. 肉眼复核铺洋红底和黑底各看一次。白边、残留的背景色在高对比色上最明显magick real_alpha.png\(clone-background#ff00ff-flatten\)\\(-clone0-backgroundblack-flatten\)-delete0append preview.png5. 看透明区域下面“藏”了什么颜色magick real_alpha.png-alphaoff hidden_rgb.png全透明像素的 RGB 值并不会被清空-alpha off之后就能看到。有时会发现透明区域下面其实是完整的原背景这说明抠图只改了 Alpha没有处理颜色。五、踩坑记录坑 1RGBA 转 RGB透明区域变黑。Pillow 的convert(RGB)会直接丢掉 Alpha透明像素原本存的是什么颜色就显示什么颜色常见的情况是变黑。另外直接把 RGBA 图像存成 JPEG 会报错OSError: cannot write mode RGBA as JPEG。正确的做法是先铺一层底色再转换fromPILimportImage imImage.open(halo.png).convert(RGBA)bgImage.new(RGBA,im.size,(255,255,255,255))Image.alpha_composite(bg,im).convert(RGB).save(out.jpg,quality92)用命令行的话是magick halo.png -background white -flatten -quality 92 out.jpg。坑 2白边不是 Alpha 的问题而是颜色的问题。在白底上抠出来的图边缘像素的 RGB 已经混进了白色Alpha 值再正确也会发亮。处理办法有两个一是最好用原始素材重新导出二是在设计软件里对边缘做“去边/收缩选区 1px”一类的处理不过这样会损失一点细节。坑 3半透明并不一定比硬边好。图标、像素风、需要切成 1 位透明 GIF 的素材本来就应该是硬边。判断标准是用途不是“半透明越多越高级”。坑 4传输过程中透明丢失。图片经过某些聊天工具、网页上传或压缩插件转一道可能会被重新编码成 JPEG透明信息也就没了。交付透明素材时尽量打包成压缩文件发送对方收到后再跑一遍上面的检查。坑 5只看缩略图。系统缩略图和部分图片预览会自动给透明区域铺白底或者铺棋盘格用它们判断透明并不可靠。六、一份可以直接用的验收清单拿到一批“透明 PNG”时我现在按这个顺序检查magick identify -format %f %m %[opaque]\n *.png格式是不是真的 PNG/WebP有没有opaqueTrue的文件python3 check_alpha.py *.png看透明占比、硬边还是半透明有没有白边或棋盘格提示挑出有提示的图生成洋红底和黑底的预览人工复看需要转 JPEG 的先铺底色再转交付时打包发送不走会重新压缩的通道。整套检查跑下来几十张图大概一两分钟。比起海报上线后再返工这点时间花得值。脚本可以随便改阈值适配自己的素材。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑