做全托管供货的,最近应该都被图片质检折磨得不轻。我自己经手过的商品图,被打回的原因里十张有八张是中文文字、水印和二维码。平台要的是干净、本地化、信息明确的图,供应商给过来的却常常是挂着“厂家直销”“包邮”横幅的淘宝风原图。靠PS一张张修不现实,上架节奏又卡在那儿。这个问题的正解是用AI批量处理:OCR把文字位置找出来,图像修复算法把文字抹掉,几分钟跑完几千张图。这篇文章就把这套流水线的思路、代码和踩坑记录都摊开讲一遍。
1. 为什么全托管平台的图片质检总是卡在中文和水印上
1.1 全托管模式对图片的要求为什么那么苛刻
全托管模式的核心是“供货商只管供货”,定价、销售、物流、售后全部由平台负责。平台拿着供货商的图片去面对海外消费者,图片就是商品的“脸”。这个时候出现中文,等于把“我是跨区供货商”写在脸上,不仅影响转化率,还容易触发平台风控对“非本地化内容”的限制。更麻烦的是水印和二维码,这类元素在平台看来是明显的导流嫌疑,审核机制对所有疑似导流元素都是零容忍,所以一看到就驳回。
很多供货商一开始想不通:我拍的商品图明明很清楚,为什么就是不通过?换个角度就明白了。平台不是看图好不好看,而是看这张图能不能直接投放到海外市场。一张挂满中文活动横幅的图,哪怕商品质量再好,审核也不敢放行,因为放上去的结果大概率是差评、退货、投诉。平台承担的运营成本比供货商高得多,审核严是必然的。
在这个逻辑下,“清洗中文与水印”就不是可做可不做的美化,而是硬性准入条件。谁能在不损伤商品细节的前提下,把图片处理得干净、本地化,谁的上架速度就快一步。这也是这篇文章存在的意义:把“人工慢慢P图”变成“机器批量处理”。
1.2 高频驳回原因清单:你的图很可能就栽在这些地方
我在实际处理中整理过一批高频驳回原因,基本可以覆盖绝大多数情况:
- 中文文字或中文拼音。这是最常见的,哪怕是几个中文字符,也属于非本地化内容。
- 水印、Logo、品牌标识。普通供货商没有品牌授权,加了Logo只会让平台觉得有侵权风险。
- 二维码、网址、联系方式。导流意图过于明显,审核看到基本直接驳回。
- “包邮”“特价”“现货”等促销文案。平台要求图片是商品信息载体,不是宣传物料。
- 边框、拼图、箭头、圈注等修饰元素。这些会干扰消费者对商品本身的判断。
- 背景杂乱、背景色不统一。多张主图背景颜色对不上,listing整体观感差,也容易被驳回。
这些驳回原因里,文字和水印是最适合用AI批量解决的。原因有二:一是文字和水印在图片上位置分散、形态多样,人工处理非常耗时;二是检测和清除这类元素的技术已经相当成熟,OCR加图像修复的组合能覆盖绝大多数场景。下面我从技术选型开始拆解。
2. 技术选型拆解:OCR、水印检测与图像修复怎么搭配
2.1 文字检测与识别:用OCR把中文文字精确框出来
OCR,光学字符识别,本质上是一个“从像素到文本”的感知任务。它分为两个阶段:检测阶段把文字区域用矩形框或多边形框出来,识别阶段把框里的像素内容翻译成字符串。商品图片里的中文文字,只要不是花体字或极低对比度,现代OCR模型都能稳稳找出来。
我优先用的是PaddleOCR。原因很直接:它的中文识别精度在开源方案里属于第一梯队,文字检测框的输出很规整,支持中英文混排识别,而且模型体积适中、可以离线跑。备选方案是EasyOCR,接口更简单,但中文识别精度和速度都弱一些。对于只偶尔处理一两百张图的场景,EasyOCR也够用;我处理批量供货图,一直用的是PaddleOCR。
这里要提醒一句:OCR检测器会把图片上所有文字都框出来,包括商品本身自带的英文说明、包装上的小标签。所以流程里必须有一个过滤环节,决定哪些框要清除、哪些框要保留。后面3.2的代码里我会演示具体怎么过滤。
2.2 水印与Logo检测:从颜色到语义分割的实用组合
水印并不全是文字,还有半透明的Logo图标和无文字的图形水印。这一类OCR无能为力,需要另一套检测思路。最实用的组合是“传统CV加颜色分析”:半透明水印会在背景上形成稳定的色差,通过灰度化、阈值分割、形态学闭运算把水印区域转成掩码;如果水印是固定位置且背景是纯色,直接取边框背景色填充是最快的。
还有一类做法叫“背景差异法”,适合同一个商品有多张图的场景。多张图里商品本身会变,但水印位置通常固定,用中值滤波或帧差法一减,就能把水印轮廓分离出来。这种方法在实验室效果不错,但实际供货商拿来的图往往只有一张角度的主图,能用的场景不多。所以我的结论是:90%的图片水印问题,文字水印用OCR解决,非文字水印先试颜色阈值,再不行就靠图像修复兜底。
2.3 图像修复算法:怎么把文字抹掉还不留痕
检测到文字和水印区域之后,下一步是把这些区域从画面里“抹掉”,并且让周围像素自然衔接。这个技术叫图像修复,专业术语是inpainting。它的核心思路是根据区域周围的颜色、纹理、结构信息,推测被遮挡部分的合理内容。
OpenCV自带的inpaint函数有两种算法:INPAINT_TELEA(快速行进法)适合小面积条纹和文字,速度快;INPAINT_NS(偏微分方程)在平滑背景上效果更细腻。对于白底、浅灰底这类干净背景,OpenCV的inpaint已经够用。真正棘手的是复杂纹理背景,比如木纹、布料、草地,这时候OpenCV会把纹理抹成一团糊。我自己的方案是,复杂背景一律换LaMa模型。LaMa是专门针对大块遮挡和复杂场景设计的生成式修复模型,修复结果在视觉上自然很多,代价是需要额外加载权重、占用显存。
实际使用中要有一个明确预期:修复是“视觉上说得过去”,不是“高清重建”。如果原图分辨率高,修复区域又小,肉眼基本看不出痕迹;如果文字又大又在复杂背景上,修复后多少会有一点点柔化,这是正常的。下面流水线设计会围绕这个预期来做。
2.4 完整处理流水线的设计:先检测后修复
把上面的组件串起来,一个可复现的流水线长这样:
原图 → 可选预处理(增强对比度) → OCR检测文本框 → 过滤出需要清除的中文/水印 → 对检测框做外扩 → 生成掩码 → 图像修复 → 后处理 → 输出干净图。
为什么推荐先检测后修复,而不是直接对整个背景做填充?因为商品主体不能被破坏。直接填充背景会把商品边缘也抹掉,检测加修复则能精准控制改动范围。检测环节要尽量保守,宁可少清一个框,也不要误清商品本身;修复环节反而可以放手一点,因为修复区域本来就应该是背景或非关键信息。整个流水线跑下来,真正需要人工介入的只有两类:半透明到几乎看不见的水印,以及带复杂纹理的背景。这两类我在第4章专门给出排查方案。
3. 手把手搭建批量清洗流水线(可直接照抄)
3.1 环境准备与依赖安装:CPU也能跑
先用pip把依赖装齐。我按PaddleOCR 2.x版本为例,这个版本的接口稳定,社区资料也多:
pip install opencv-python numpy pillow paddlepaddle paddleocr==2.7.3如果你的机器有NVIDIA显卡,并且CUDA环境正常,可以把paddlepaddle换成paddlepaddle-gpu,推理速度能快好几倍。没有GPU也没关系,纯CPU跑一张图大概1到3秒,几千张图也就是几十分钟到一两个小时,放在晚上跑完全能接受。
安装完成之后,第一次运行PaddleOCR会自动下载检测模型和识别模型权重。下载依赖网络环境,我建议提前把权重下载好,放到用户目录下的.paddleocr文件夹里,离线跑会省心很多。
3.2 核心代码:批量清理全流程
下面这套脚本我直接用在供货图的批量处理上。逻辑分四步:初始化OCR、单张图清洗、目录遍历、生成日志。注意,PaddleOCR不同版本的返回值结构有差异,如果你用的是3.x版本,需要按最新文档把解析部分微调一下。
import os import glob import time import csv import shutil import cv2 import numpy as np from paddleocr import PaddleOCR # 是否只清洗中文。如果你的图上有英文Logo也要清,改成False CLEAN_ONLY_CHINESE = True # 检测框外扩像素,中文笔画比较舒展,给足余量 PAD_X = 20 PAD_Y = 15 # OpenCV inpaint 半径,纯色背景5到10都行 INPAINT_RADIUS = 5 # 小于这个面积的检测框当成噪点忽略 MIN_BOX_AREA = 300 ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False) def contains_chinese(text): return any('\u4e00' <= ch <= '\u9fff' or '\u3400' <= ch <= '\u4dbf' for ch in text) def box_to_rect(box): xs = [p[0] for p in box] ys = [p[1] for p in box] return int(min(xs)), int(min(ys)), int(max(xs)), int(max(ys)) def clean_image(img_path, out_path): img = cv2.imread(img_path) if img is None: return False, 0 h, w = img.shape[:2] result = ocr.ocr(img, cls=True) mask = np.zeros((h, w), dtype=np.uint8) found = 0 if result and result[0]: for item in result[0]: box, (text, score) = item[0], item[1] if CLEAN_ONLY_CHINESE and not contains_chinese(text): continue x1, y1, x2, y2 = box_to_rect(box) rect_w = x2 - x1 rect_h = y2 - y1 if rect_w * rect_h < MIN_BOX_AREA: continue x1 = max(0, x1 - PAD_X) y1 = max(0, y1 - PAD_Y) x2 = min(w, x2 + PAD_X) y2 = min(h, y2 + PAD_Y) cv2.rectangle(mask, (x1, y1), (x2, y2), 255, -1) found += 1 if found == 0: return False, 0 cleaned = cv2.inpaint(img, mask, INPAINT_RADIUS, cv2.INPAINT_TELEA) cv2.imwrite(out_path, cleaned, [cv2.IMWRITE_JPEG_QUALITY, 95]) return True, found def batch_clean(src_dir, out_dir, log_path): os.makedirs(out_dir, exist_ok=True) files = glob.glob(os.path.join(src_dir, '*.jpg')) files += glob.glob(os.path.join(src_dir, '*.jpeg')) files += glob.glob(os.path.join(src_dir, '*.png')) files += glob.glob(os.path.join(src_dir, '*.webp')) with open(log_path, 'w', newline='', encoding='utf-8') as fp: writer = csv.writer(fp) writer.writerow(['file', 'boxes_removed', 'cleaned', 'time']) total = len(files) cleaned_count = 0 for idx, f in enumerate(files, 1): out = os.path.join(out_dir, os.path.basename(f)) try: ok, boxes = clean_image(f, out) if ok: cleaned_count += 1 else: # 没有文字,直接复制原图,保持输出目录完整 shutil.copy2(f, out) writer.writerow([os.path.basename(f), boxes, ok, time.strftime('%Y-%m-%d %H:%M:%S')]) print(f'[{idx}/{total}] {os.path.basename(f)} -> {boxes}处文字, cleaned={ok}') except Exception as e: print(f'[{idx}/{total}] {os.path.basename(f)} 处理失败: {e}') shutil.copy2(f, out) print(f'完成: 共{total}张, 清理{cleaned_count}张') if __name__ == '__main__': batch_clean( src_dir='./input_images', out_dir='./output_images', log_path='./clean_log.csv' )这个脚本可以直接跑。源目录下所有图片会统一处理,有文字的保存清洗后的版本,没文字的复制原图,输出目录完整不漏图。日志文件记录了每个文件被清理的区域数量,方便后面抽检和对账。
3.3 关键参数怎么调:直接影响通过率和速度
参数不是设一次就能通吃所有图,我调整过的几个经验值整理成表:
| 参数 | 推荐值 | 作用 | 我的调参建议 |
|---|---|---|---|
| CLEAN_ONLY_CHINESE | True | 只清理中文,保留英文 | 如果图上有英文水印,改成False |
| PAD_X / PAD_Y | 20 / 15 | 扩宽修复范围 | 字体越大,越要加大,但别超过字体的50% |
| INPAINT_RADIUS | 5 | 修复算法取样半径 | 纯色背景下可以增加到10 |
| MIN_BOX_AREA | 300 | 过滤噪点框 | 检测到大量误检时提高到800 |
| use_angle_cls | True | 纠正倾斜文字 | 拍摄角度不规则时开启 |
这里重点说下PAD_X和PAD_Y。中文不像英文单词那样每个字之间有清晰空格,OCR给出的检测框往往紧紧包着文字,如果直接拿这个框去做修复,文字的边缘残留会很严重。多扩展20到30像素,是给文字笔画留出“缓冲带”。但也不是越大越好,扩太多会把商品本身一起修掉,尤其是文字挨着商品边缘的情况。
检测阈值在脚本里没有单独列出来,因为PaddleOCR的det_db_thresh默认0.3已经比较均衡。如果你要处理的图大量是浅色水印,可以把PaddleOCR初始化参数里的det_db_thresh调低到0.1,代价是误检增多。我一般不动这个值,而是靠增强预处理来捞浅色水印。
3.4 日志留痕与人工抽检:别把流水线当黑盒
批量脚本跑完,直接传到平台提交?千万不要。我每次跑完都会做两件事,这两件事帮我避开了大量返工:
第一,看日志。clean_log.csv里记录了每个文件清理了几处文字。如果一个文件显示0处文字,但你自己知道原图明显有中文横幅,那说明OCR漏检了,这类文件名要单独拎出来再看一遍。第二,人工抽检。我从输出目录里随机抽10%到20%的图,用看图软件放大到200%,重点看四角和商品边缘有没有文字残留、修复区域有没有明显色块。这个环节不会花太多时间,但能把通过率从“碰运气”变成“可控”。
有一个小习惯值得分享:批量处理前,先拿三张不同风格的图做测试,一张白底、一张暖色底、一张带复杂纹理,确认参数对这三张都OK,再放开跑全量。否则参数不合适,几千张图跑完才发现大面积出错,返工成本就太高了。
4. 实操中高频踩坑与排查速查表
4.1 浅色半透明水印检测不到怎么办
OCR对清晰文字有效,但供货商发来的原图经常有半透明压印,比如品牌Logo浮在白色背景上,肉眼能看到,OCR就是识别不出来。原因是模型训练时见过的大多是清晰印刷体,半透明水印对比度太低。
我的处理思路是“增强后再跑一次”。把原图转灰度,做CLAHE自适应直方图均衡化,或者做Gamma矫正,把浅色水印和背景的差距拉大。有时候同一张图,原图跑一遍OCR、增强图再跑一遍OCR,把两轮检测框合并,能多捞出不少水印。注意两轮检测结果会重复,合并前需要去重,简单的做法是两个检测框中心距离小于10像素的当成同一个。
更粗暴但有效的方式只适用于纯色背景:取图片四角的颜色平均值作为背景色,把背景区域和这个平均色做差,差值超过阈值的部分全当成水印掩码,扩大范围后直接用背景色填充。这个方案对白底图特别好用,而且速度比OCR快很多。
4.2 商品自带文字被误删怎么办
这个问题几乎每个人都会遇到。商品包装上的英文说明、标签上的品牌心号,这些属于商品固有信息,平台一般允许保留。但如果你的OCR框把它们也框出来了,脚本里CLEAN_ONLY_CHINESE为True时,它们不会被清掉。只有那些是中文的固有信息会被清。这个时候就要人工判断,比如国内生产的商品包装上印了中文配料表,那整张图其实不适合直接投放海外,更好的处理是重新拍摄或换包装,而不是靠P图清洗。
如果英文水印也要清,比如供货商自己贴的英文Logo贴在商品图上,那就把CLEAN_ONLY_CHINESE改成False。有一个辅助技巧:真正要清的水印基本都在图片边缘区域,可以通过“只处理距离图片边界一定范围内的检测框”来减少误删。商品主体区域内的文字,大概率是商品固有信息。
4.3 修复后背景出现色斑和断裂纹理
OpenCV的inpaint在纯色背景上很干净,但背景一复杂就容易露馅。我曾经处理过一张木纹桌面的商品图,文字是清掉了,可修复区域变成一个小色块,一眼就能看出来。后来换了思路:如果是布料、木纹、大理石这类有规律的纹理背景,不要盲目加大修复半径,而是尝试用LaMa模型处理,修复结果会带上纹理走向,肉眼基本看不出来。
如果不想引入额外模型,还有一个折中方案:修复完成后对掩码区域做一次轻微的高斯模糊,让边缘过渡更柔和。但这只能缓解,不能根治。另外,修复时INPAINT_RADIUS不建议调太大,超过15之后容易把文字周围的颜色“抹”成一大片,反而更显眼。
4.4 批量处理太慢的优化方案
全量几千张图,纯CPU一张一张跑确实慢。我的优化优先级是这样排的:
第一,确保OCR模型只初始化一次,不要每张图都重新加载。第二,对超大图先做缩放检测再映射回原图。比如图片分辨率超过3000像素,先缩放到1500像素,OCR检测得到文本框后,按比例把坐标放大回原图,再在原图上做修复。这样OCR耗时能降低到原来的三分之一,坐标有少量误差可以通过PAD_X和PAD_Y吸收。第三,多进程并行。每张图独立处理,可以开多个进程,每个进程加载一份OCR模型,按文件列表平均分配。
要特别提醒的是,PaddleOCR虽然支持批量输入图片列表,但实际加速效果要看显存和内存。我自己的经验是CPU多进程比单进程批量输入更稳,进程数量控制在CPU物理核心数左右,开多了反而互相抢资源。
4.5 二次驳回的排查思路
批量清洗后提交,如果平台还是驳回,先别急着重跑脚本。我在这个环节学到的教训是:驳回原因要仔细看,是“图片包含中文”,说明漏检了;“图片包含未授权水印”,说明纯图形水印没清掉;“图片与主图背景不一致”,说明不是文字问题,是拍摄端没有统一背景色。
针对不同原因,处理方式完全不同。漏检中文,就对该图单独做增强预处理后再跑一遍OCR。未授权水印,就得检查是不是半透明Logo区域,用4.1里的纯背景差值法单独处理。背景不一致,只能回到拍摄端重新输出素材,靠AI补背景反而容易显得假。
我的经验是,建立一个小小的抽检台账,每次处理后随机截图留档,记录图片文件名、处理日期、处理方式。再驳回的时候,拿着台账和截图去和平台沟通过程会顺畅得多。这个小习惯看起来不起眼,但次数多了能省下大量瞎试的时间。
最后再分享一点我自己的体会:AI清洗说得再好,也只是“事后补救”。供货商想长期稳定过审,最好在拍摄端就落实这些硬性要求:相机设置里关掉自动加时间戳,拍摄时不要贴中文标签,背景统一纯色。这样生成的图片通过率自然高,AI最多负责兜底和抽查。这套流水线现在还在持续迭代,比如半透明Logo检测、复杂背景修复,目前我还只是做到了“够用”。如果大家在实际跑的时候遇到新的驳回场景,欢迎拿图来一起研究。