批量处理多张图的方法,我在脚本里加了循环
本文是一篇面向实际工程落地的技术实践笔记,聚焦于如何将阿里开源的“万物识别-中文-通用领域”模型从单图推理升级为批量图像识别能力。不讲抽象原理,不堆砌参数,只说你真正需要的操作:怎么改几行代码,让脚本自动遍历文件夹里的几十张图、逐张识别、统一输出结果,并避免常见坑点。适合已成功运行过单图推理、正准备接入业务流程的开发者。
你可能已经照着教程跑通了第一张图——比如bailing.png,看到终端输出“动物”“人物”“植物”等中文标签,心里踏实了一半。但真实场景中,没人只传一张图:电商要扫千张商品图打标,内容平台要批量审核封面图,智能相册要自动归类几百张家庭照片。这时候,手动改路径、反复执行python 推理.py显然不可持续。本文就解决这个卡点:用最轻量的方式,在原生脚本基础上加入循环逻辑,零依赖、零重构、5分钟完成升级。
核心思路非常朴素:把原来写死的一张图路径,换成一个图片列表;把单次推理封装成函数;再用for遍历列表。没有引入新框架,不改动模型加载逻辑,所有修改都在你熟悉的推理.py文件里,改完即用,结果可读、可存、可扩展。
1. 理解原始脚本的单图结构
在动手改之前,先看清原始推理.py的骨架。它本质是三段式结构:加载模型 → 加载单图 → 推理输出。我们不需要重写,只需在关键位置“插针”。
1.1 原始流程拆解(精简版)
# -*- coding: utf-8 -*- import torch from PIL import Image from transformers import AutoModel, AutoProcessor # 【阶段一】模型加载(只做一次) model_name = "bailian/wwts-visual-recognition-base" processor = AutoProcessor.from_pretrained(model_name) model = AutoModel.from_pretrained(model_name) device = "cuda" if torch.cuda.is_available() else "cpu" model.to(device) # 【阶段二】单图加载(当前是硬编码路径) image_path = "/root/workspace/bailing.png" # ← 这里是单点入口 image = Image.open(image_path).convert("RGB") # 【阶段三】单图推理与输出 inputs = processor(images=image, text=["动物", "人物", "交通工具"], return_tensors="pt").to(device) with torch.no_grad(): outputs = model(**inputs) probs = outputs.logits_per_image.softmax(dim=1)[0] top_probs, top_labels = probs.topk(3) class_names = ["动物", "人物", "交通工具"] for i in range(3): idx = top_labels[i].item() print(f"{image_path} → {class_names[idx]} ({top_probs[i].item():.3f})")注意两个关键事实:
- 模型加载(阶段一)耗时且占用显存,必须放在循环外,否则每张图都重载,速度暴跌、显存爆满;
- 图像加载(阶段二)和推理(阶段三)是轻量操作,可以安全放入循环内。
这决定了我们的改造边界:只动阶段二和三,阶段一原封不动。
2. 批量处理四步法:从路径到结果
批量不是魔法,是清晰的步骤链。以下四步严格对应你将在/root/workspace/推理.py中做的真实修改,每步附可直接粘贴的代码块和避坑提示。
2.1 第一步:导入必要模块并定义图片目录
在文件顶部import区域下方,添加两行:
import os from pathlib import Path然后,在模型加载完成后(即model.to(device)下方),定义你要处理的图片所在文件夹:
# 【新增】指定图片所在目录(请按实际路径修改) image_dir = "/root/workspace/images" # ← 改成你的图片文件夹路径 # 【新增】获取该目录下所有支持的图片文件(.png/.jpg/.jpeg) supported_exts = {".png", ".jpg", ".jpeg", ".PNG", ".JPG", ".JPEG"} image_paths = [ str(p) for p in Path(image_dir).iterdir() if p.is_file() and p.suffix in supported_exts ] print(f"发现 {len(image_paths)} 张待识别图片") if not image_paths: print(" 警告:未找到任何图片,请检查路径是否正确、文件夹是否为空") exit(1)为什么这样写?
- 用
pathlib.Path比os.listdir()更安全,自动过滤子目录和隐藏文件; - 显式声明支持的后缀,避免
.webp等格式报错中断; - 提前校验数量并退出,防止空列表导致后续循环异常。
2.2 第二步:封装单图推理为函数
把原始的“加载图→预处理→推理→打印”逻辑,提取成一个独立函数。将原脚本中从image_path = ...开始到print(...)结束的所有代码,整体剪切出来,替换成以下函数定义:
def recognize_single_image(image_path, class_names, top_k=3): """ 对单张图片执行识别 :param image_path: 图片绝对路径 :param class_names: 中文提示词列表,如 ["动物", "人物"] :param top_k: 返回前K个高置信度结果 :return: [(label, prob), ...] 列表 """ try: # 加载并转换图片 image = Image.open(image_path).convert("RGB") # 预处理(复用原逻辑) inputs = processor( images=image, text=class_names, return_tensors="pt", padding=True ).to(device) # 推理 with torch.no_grad(): outputs = model(**inputs) # 计算概率并取topk probs = outputs.logits_per_image.softmax(dim=1)[0] top_probs, top_labels = probs.topk(top_k) # 组装结果 results = [] for i in range(top_labels.shape[0]): label_idx = top_labels[i].item() results.append((class_names[label_idx], top_probs[i].item())) return results except Exception as e: print(f"❌ 处理 {image_path} 时出错: {str(e)}") return []关键设计点:
- 函数接收
image_path和class_names作为参数,解耦数据与逻辑; - 包裹
try...except,单张图失败不影响其他图处理(真实场景必备); - 返回结构化结果
[(label, prob), ...],为后续导出JSON或统计打基础。
2.3 第三步:编写主循环,调用函数处理每张图
在函数定义下方,添加主循环逻辑:
# 【新增】主批量处理循环 class_names = ["动物", "人物", "交通工具", "食物", "建筑", "植物"] # ← 保持与原逻辑一致 print("\n 开始批量识别...") for i, img_path in enumerate(image_paths, 1): print(f"\n--- 第 {i}/{len(image_paths)} 张:{os.path.basename(img_path)} ---") # 调用单图识别函数 results = recognize_single_image(img_path, class_names, top_k=3) # 输出结果(保持原有风格,易读) if results: for label, prob in results: print(f" → {label} (置信度: {prob:.3f})") else: print(" → 无有效识别结果")为什么用enumerate(..., 1)?
- 让序号从1开始(非0),符合人类阅读习惯;
os.path.basename()只显示文件名,避免长路径刷屏;- 每张图用分隔线隔开,结果一目了然。
2.4 第四步:(可选)将结果保存为CSV便于分析
如果需要把结果存档或导入Excel,追加以下代码(放在循环之后):
# 【新增】导出为CSV(可选) import csv output_csv = "/root/workspace/recognition_results.csv" with open(output_csv, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["图片文件名", "Top1标签", "Top1置信度", "Top2标签", "Top2置信度", "Top3标签", "Top3置信度"]) for img_path in image_paths: results = recognize_single_image(img_path, class_names, top_k=3) row = [os.path.basename(img_path)] for i in range(3): if i < len(results): row.extend([results[i][0], f"{results[i][1]:.3f}"]) else: row.extend(["", ""]) writer.writerow(row) print(f"\n 结果已保存至 {output_csv}")优势说明:
- CSV格式兼容Excel、Tableau等工具,方便业务同学查看;
- 字段命名直白(“Top1标签”),无需额外文档解释;
- 使用
utf-8编码,确保中文不乱码。
3. 实操验证:三分钟跑通全流程
现在,你已完成了全部代码修改。接下来是验证环节,确保每一步都稳稳落地。
3.1 准备测试图片集
在/root/workspace下创建images文件夹,并放入3-5张不同类型的图片(如猫、车、建筑、食物):
mkdir -p /root/workspace/images cp /root/workspace/bailing.png /root/workspace/images/ # 再上传2-4张其他图片(如 mydog.jpg, car.jpg)用ls确认:
ls /root/workspace/images/ # 应输出类似:bailing.png mydog.jpg car.jpg3.2 执行批量脚本
进入工作目录并运行:
cd /root/workspace python 推理.py你将看到类似输出:
发现 3 张待识别图片 开始批量识别... --- 第 1/3 张:bailing.png --- → 动物 (置信度: 0.967) → 人物 (置信度: 0.021) → 植物 (置信度: 0.008) --- 第 2/3 张:mydog.jpg --- → 动物 (置信度: 0.982) → 人物 (置信度: 0.011) → 食物 (置信度: 0.003) --- 第 3/3 张:car.jpg --- → 交通工具 (置信度: 0.945) → 建筑 (置信度: 0.032) → 动物 (置信度: 0.015) 结果已保存至 /root/workspace/recognition_results.csv3.3 快速排查常见问题
| 现象 | 原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'pathlib' | Python版本过低 | 确认已激活py311wwts环境(conda activate py311wwts) |
FileNotFoundError: [Errno 2] No such file or directory: '/root/workspace/images' | image_dir路径错误 | 用ls /root/workspace/检查文件夹是否存在,注意大小写和斜杠 |
OSError: cannot identify image file | 图片损坏或格式不支持 | 用file /root/workspace/images/*查看文件类型,删除非图片文件 |
| 循环只处理1张图就停止 | image_paths列表为空 | 检查supported_exts是否包含你的图片后缀(如.JPG是大写) |
4. 进阶技巧:让批量更聪明、更省心
基础循环已能工作,但真实业务需要更多“小心思”。以下是三个经实战验证的轻量级增强技巧,全部基于现有代码微调,无需额外依赖。
4.1 技巧一:动态提示词——按文件夹名自动切换识别重点
假设你有/root/workspace/images/food/(美食图)和/root/workspace/images/product/(商品图),希望前者重点识别“火锅”“寿司”,后者识别“手机”“耳机”。只需修改主循环中的class_names定义:
# 替换原主循环中的 class_names 定义 img_folder = os.path.dirname(img_path) if "food" in img_folder.lower(): class_names = ["火锅", "寿司", "披萨", "蛋糕", "水果"] elif "product" in img_folder.lower(): class_names = ["手机", "耳机", "笔记本电脑", "手表", "充电宝"] else: class_names = ["动物", "人物", "交通工具", "食物", "建筑", "植物"] # 默认效果:同一脚本,自动适配不同业务场景,无需维护多个版本。
4.2 技巧二:阈值过滤——只保留高置信度结果
避免低质量输出干扰判断。在recognize_single_image函数返回前,添加过滤:
# 在函数末尾,results组装完成后,插入: min_confidence = 0.3 # ← 设定最低置信度阈值 results = [(label, prob) for label, prob in results if prob >= min_confidence]效果:若某张图所有结果置信度都低于0.3,results为空,输出“无有效识别结果”,干净利落。
4.3 技巧三:进度条可视化——告别黑屏等待
对大量图片(>100张),加一行进度提示提升体验:
# 在主循环开头,import区添加: from tqdm import tqdm # 将主循环改为: for img_path in tqdm(image_paths, desc="识别中", unit="图"): # ... 原有循环体 ...注意:tqdm未预装,需先执行pip install tqdm。若不想装包,可用更轻量的print(f"\r处理中: {i}/{len(image_paths)}", end="")替代。
5. 总结:批量不是终点,而是业务集成的起点
你刚刚完成的,远不止是“加了个循环”。你构建了一个可立即投入业务使用的图像识别流水线雏形:它稳定、可调试、可扩展、结果可追溯。更重要的是,所有改动都扎根于你已掌握的原始脚本,没有引入陌生概念,学习成本趋近于零。
回顾整个过程,核心经验只有三点:
- 模型加载永远在外层:这是性能的生命线,也是显存管理的铁律;
- 单图逻辑必须可复用:封装成函数,是应对未来需求变化(如加日志、加缓存、加API)的基石;
- 批量的本质是数据驱动:路径、提示词、阈值、输出格式——这些变量才是你真正需要配置的业务参数,代码只是执行容器。
下一步,你可以轻松延伸:
- 把CSV结果喂给数据库,构建带搜索的图片资产库;
- 用
Flask包一层,提供POST /recognize接口,供前端上传图片实时识别; - 将
class_names从硬编码改为读取配置文件,实现运营人员自助配置识别维度。
技术的价值,从来不在炫技,而在让复杂的事变简单。当你不再为每张图手动改路径,当结果自动归档进表格,当业务同学指着Excel说“这准确率真高”,你就知道:那个“加了循环”的脚本,已经真正活了起来。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。