FlanChanXwO commited on
Commit
6b0e0fc
·
verified ·
1 Parent(s): 4c6686f

Upload scripts/label_captchas.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. scripts/label_captchas.py +115 -3
scripts/label_captchas.py CHANGED
@@ -1,3 +1,115 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:3386e054e01fc764c9d728195ca6d9b200094b393a987bedb35cdf9709e102f4
3
- size 4292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """用 pie-xian command-a-vision 批量识别验证码:3x放大 + 每张2次读取(不一致补第3次) + 多数投票。
3
+ 用法: label_captchas.py <图片目录> <输出.json> [workers]
4
+ 输出: {filename: {label, votes:[...], count, agreed}}
5
+ """
6
+ import base64
7
+ import concurrent.futures
8
+ import glob
9
+ import json
10
+ import os
11
+ import re
12
+ import ssl
13
+ import sys
14
+ import tempfile
15
+ import urllib.error
16
+ import urllib.request
17
+ from collections import Counter
18
+
19
+ from PIL import Image
20
+
21
+ API = "https://api.pie-xian.com/v1/chat/completions"
22
+ KEY = os.environ.get("PIE_XIAN_API_KEY", "sk-Rel8iERHrmu9NDbvQhnhwTowEUnGqVvCFXMatASH8q5O4gGP")
23
+ MODEL = "command-a-vision-07-2025"
24
+ UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36"
25
+ PROMPT = "识别这张验证码图片中的字符。只输出字符本身,不要任何解释、标点或额外文字。这是登录验证码,可能包含1-4个数字。"
26
+
27
+ _CTX = ssl.create_default_context()
28
+ _CTX.check_hostname = False
29
+ _CTX.verify_mode = ssl.CERT_NONE
30
+
31
+
32
+ def call_vision(big_path):
33
+ b64 = base64.b64encode(open(big_path, "rb").read()).decode()
34
+ body = json.dumps({
35
+ "model": MODEL,
36
+ "messages": [{"role": "user", "content": [
37
+ {"type": "text", "text": PROMPT},
38
+ {"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}},
39
+ ]}],
40
+ "max_tokens": 20, "temperature": 0,
41
+ }).encode()
42
+ req = urllib.request.Request(API, data=body, headers={
43
+ "Authorization": "Bearer " + KEY, "Content-Type": "application/json", "User-Agent": UA})
44
+ d = json.loads(urllib.request.urlopen(req, timeout=90, context=_CTX).read())
45
+ code = re.sub(r"[^0-9A-Za-z]", "", d["choices"][0]["message"]["content"])
46
+ if not code:
47
+ raise RuntimeError("empty output")
48
+ return code
49
+
50
+
51
+ def make_big(src, bigdir):
52
+ name = os.path.basename(src)
53
+ dst = os.path.join(bigdir, name)
54
+ im = Image.open(src).convert("RGB")
55
+ im = im.resize((im.width * 3, im.height * 3), Image.LANCZOS)
56
+ im.save(dst)
57
+ return dst
58
+
59
+
60
+ def label_one(big):
61
+ votes = []
62
+ for _ in range(2):
63
+ for attempt in range(3):
64
+ try:
65
+ votes.append(call_vision(big))
66
+ break
67
+ except Exception:
68
+ continue
69
+ c = Counter(votes)
70
+ if len(votes) >= 2 and c.most_common(1)[0][1] >= 2:
71
+ label, n = c.most_common(1)[0]
72
+ return {"label": label, "votes": votes, "count": n, "agreed": True}
73
+ for attempt in range(3):
74
+ try:
75
+ votes.append(call_vision(big))
76
+ break
77
+ except Exception:
78
+ continue
79
+ if not votes:
80
+ return {"label": None, "votes": [], "count": 0, "agreed": False, "error": "all reads failed"}
81
+ c = Counter(votes)
82
+ label, n = c.most_common(1)[0]
83
+ return {"label": label, "votes": votes, "count": n, "agreed": n == len(votes)}
84
+
85
+
86
+ def main():
87
+ d = sys.argv[1]
88
+ out = sys.argv[2]
89
+ workers = int(sys.argv[3]) if len(sys.argv) > 3 else 8
90
+ with tempfile.TemporaryDirectory(prefix="sp_big_") as bigdir:
91
+ files = sorted(glob.glob(os.path.join(d, "*.png")))
92
+ print(f"images={len(files)} workers={workers}", flush=True)
93
+ bigs = {f: make_big(f, bigdir) for f in files}
94
+ results = {}
95
+ with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as ex:
96
+ futs = {ex.submit(label_one, bigs[f]): f for f in files}
97
+ done = 0
98
+ for fut in concurrent.futures.as_completed(futs):
99
+ f = futs[fut]
100
+ try:
101
+ results[os.path.basename(f)] = fut.result()
102
+ except Exception as e:
103
+ results[os.path.basename(f)] = {"label": None, "votes": [], "count": 0, "error": str(e)}
104
+ done += 1
105
+ if done % 10 == 0 or done == len(files):
106
+ print(f"progress {done}/{len(files)}", flush=True)
107
+ with open(out, "w") as fh:
108
+ json.dump(results, fh, ensure_ascii=False, indent=1)
109
+ ok = sum(1 for v in results.values() if v.get("label"))
110
+ agree = sum(1 for v in results.values() if v.get("agreed"))
111
+ print(f"DONE labeled={ok}/{len(files)} full-agree={agree} -> {out}")
112
+
113
+
114
+ if __name__ == "__main__":
115
+ main()