返回前期准备

本机材料处理方法

完成三轮后的拆表与计数可用作者本机整理工具,无需Python。先按教程确认归属待核实的照片,再选择已核对来源的CSV;生成三份新表后回到04安排学生反馈和课堂讲评。下面的Python方法仍用于换用自己的材料时遮名、对应姓名。

练习包的“演示材料”已经是原运行使用的7张遮名合成图片、代号表和参考答案,跟练不需要先安装图片处理库。以下是原稿的本机准备方法,供你处理自己的材料时按需采用;没有验证所有操作系统、校内系统和真实字迹。

少量照片可以先在本机遮姓名,再用表格软件整理“学号、代号”两列。编号必须和照片一致;真实姓名映射留在另一个私有目录。学校不允许交出学号时,改用经允许的座号,两处保持同一套编号。工具的数据条件不满足时,先别上传真实作业。

批量脚本要求名单副本第一行是标题,第一列学号、第二列姓名,学号非空且不重复;先安装 Python 3 及 Pillow、openpyxl。脚本按固定矩形遮姓名,照片歪斜或姓名位置不同会漏遮、也可能盖住题目,运行后必须逐张检查。

把下面所有“/完整路径/…”换成实际位置。第一行进入材料包解压目录,该目录下应有“本机处理脚本”;照片、名单和私有目录使用各自完整路径。引号保留,路径含空格也按一个参数读取。逐行执行:

cd "/完整路径/练习材料"
python3 -m pip install pillow openpyxl
python3 "本机处理脚本/遮姓名.py" "/完整路径/原图文件夹" "/完整路径/班级名单-副本.xlsx" "给AI文件夹" "/完整路径/私有文件夹"

输出的照片和“代号表.csv”位于解压目录下新建的“给AI文件夹”;姓名映射位于你指定的私有目录。将它放在给AI目录之外。此命令只遮姓名,若学号也不允许交出,仍要在本机同时替换照片和代号表中的编号。

将参考答案复制进“给AI文件夹”。最后换姓名时,只给脚本使用你已核对的反馈表;未归属页面仍单独保留,不通过换姓名脚本补猜。

仍在同一个解压目录运行。确认“给AI文件夹”里已保存你核对过的“批改反馈表-待核实版.csv”,把映射文件路径换成上一步的实际私有位置:

python3 "本机处理脚本/换回真名.py" "给AI文件夹/批改反馈表-待核实版.csv" "/完整路径/私有文件夹/姓名对照表-不要发给AI.csv"

默认输出在映射文件旁,名为“批改反馈表-待核实版-真名.csv”。这是本机副本,未归属页不会因此自动得到归属。

输出留在私有目录,之后继续让智能体修改时,仍交代号版。准备脚本不代替教师确认评分口径、页面归属或评语。

换回真名.py

# 用法:python3 换回真名.py 反馈表.csv 私有文件夹/姓名对照表-不要发给AI.csv [输出路径]
# 在本机把反馈表里的代号换回姓名。输出写到私有文件夹(不写进给AI文件夹);
# 不给输出路径时,默认写在对照表旁边,文件名为“原文件名-真名.csv”。
import sys, os, csv
if len(sys.argv) not in (3, 4):
    sys.exit("用法:python3 换回真名.py 反馈表.csv 姓名对照表-不要发给AI.csv [输出路径]")
tab, priv = sys.argv[1], sys.argv[2]
base = os.path.splitext(os.path.basename(tab))[0] + "-真名.csv"
outp = sys.argv[3] if len(sys.argv) == 4 else os.path.join(os.path.dirname(os.path.abspath(priv)), base)
ab = lambda p: os.path.realpath(p)
ai_dir = ab(os.path.dirname(os.path.abspath(tab)))
od = ab(os.path.dirname(os.path.abspath(outp)))
if od == ai_dir or od.startswith(ai_dir + os.sep):
    sys.exit("输出路径在反馈表所在的文件夹(给AI)里,换到私有文件夹。")
m = {r["代号"]: r["姓名"] for r in csv.DictReader(open(priv, encoding="utf-8-sig"))}
text = open(tab, encoding="utf-8-sig").read()
for code in sorted(m, key=len, reverse=True): text = text.replace(code, m[code])
open(outp, "w", encoding="utf-8-sig").write(text); print("已写出", outp)

遮姓名.py

# 用法:python3 遮姓名.py 原图文件夹 名单副本.xlsx 新的给AI文件夹 私有文件夹 [工作表名]
# 在本机把每张照片左上角的姓名栏涂白,生成“学号—代号”表;真名对照表写进私有文件夹。不联网,不改原图。
# 名单副本要求:首行是标题,第 1 列学号、第 2 列姓名,学号不空、不重复,写法和照片上的学号一致。
# 需要:pip install pillow openpyxl
import sys, os, csv, openpyxl
from PIL import Image, ImageDraw
if len(sys.argv) not in (5, 6):
    sys.exit("用法:python3 遮姓名.py 原图文件夹 名单副本.xlsx 新的给AI文件夹 私有文件夹 [工作表名]")
src, roster, out, priv_dir = sys.argv[1:5]
sheet = sys.argv[5] if len(sys.argv) == 6 else None
# 姓名栏在照片里的大致位置(占整张照片宽高的比例)。照片拍法不同就改这四个数。
LEFT, TOP, RIGHT, BOTTOM = 0.0, 0.10, 0.43, 0.175
ab = lambda p: os.path.realpath(p)
if ab(out) == ab(src) or ab(out).startswith(ab(src) + os.sep):
    sys.exit("给AI文件夹不能是原图文件夹或在它里面,换一个新文件夹名。")
if ab(priv_dir) == ab(out) or ab(priv_dir).startswith(ab(out) + os.sep):
    sys.exit("私有文件夹不能放在给AI文件夹里面。")
if os.path.exists(out) and os.listdir(out):
    sys.exit(f"{out} 已存在且不是空的。每批作业请用一个新的空文件夹,免得混进上一批的照片或反馈表。")
wb = openpyxl.load_workbook(roster)
if sheet and sheet not in wb.sheetnames:
    sys.exit(f"名单里没有工作表“{sheet}”,现有:{wb.sheetnames}")
ws = wb[sheet] if sheet else wb.active
print("读取工作表:", ws.title, ";标题行:", [c.value for c in ws[1]][:2])
rows, seen = [], set()
for n, r in enumerate(ws.iter_rows(min_row=2, values_only=True), 2):
    if not r or all(v in (None, "") for v in r[:2]): continue
    sid, name = ("" if r[0] is None else str(r[0]).strip()), ("" if len(r) < 2 or r[1] is None else str(r[1]).strip())
    if not sid or not name: sys.exit(f"名单第 {n} 行学号或姓名为空,请先补全名单副本。")
    if sid in seen: sys.exit(f"名单第 {n} 行学号 {sid} 重复。")
    seen.add(sid); rows.append((sid, name))
os.makedirs(out, exist_ok=True); os.makedirs(priv_dir, exist_ok=True)
k = 0
for fn in sorted(os.listdir(src)):
    if not fn.lower().endswith((".jpg", ".jpeg", ".png")): continue
    im = Image.open(os.path.join(src, fn)).convert("RGB"); w, h = im.size
    ImageDraw.Draw(im).rectangle([LEFT*w, TOP*h, RIGHT*w, BOTTOM*h], fill="white")
    im.save(os.path.join(out, fn), quality=90); k += 1
with open(os.path.join(out, "代号表.csv"), "w", newline="", encoding="utf-8-sig") as f:
    wr = csv.writer(f); wr.writerow(["学号", "代号"])
    for i, (sid, _) in enumerate(rows, 1): wr.writerow([sid, f"S{i:02d}"])
priv = os.path.join(priv_dir, "姓名对照表-不要发给AI.csv")
with open(priv, "w", newline="", encoding="utf-8-sig") as f:
    wr = csv.writer(f); wr.writerow(["代号", "学号", "姓名"])
    for i, (sid, name) in enumerate(rows, 1): wr.writerow([f"S{i:02d}", sid, name])
print(f"已处理照片 {k} 张、名单 {len(rows)} 人。给AI文件夹:{out};真名对照表:{priv}")