captcha-font-sdk 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. captcha_font_sdk-0.0.1/LICENSE +21 -0
  2. captcha_font_sdk-0.0.1/PKG-INFO +261 -0
  3. captcha_font_sdk-0.0.1/README.md +234 -0
  4. captcha_font_sdk-0.0.1/captcha_font_sdk/__init__.py +72 -0
  5. captcha_font_sdk-0.0.1/captcha_font_sdk/background_index.py +74 -0
  6. captcha_font_sdk-0.0.1/captcha_font_sdk/bitmap_ops.py +61 -0
  7. captcha_font_sdk-0.0.1/captcha_font_sdk/component_extractor.py +82 -0
  8. captcha_font_sdk-0.0.1/captcha_font_sdk/diff_engine.py +25 -0
  9. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_batch.py +85 -0
  10. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_dataset.py +108 -0
  11. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_extractor.py +95 -0
  12. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_features.py +45 -0
  13. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_images.py +57 -0
  14. captcha_font_sdk-0.0.1/captcha_font_sdk/font_glyph_slots.py +44 -0
  15. captcha_font_sdk-0.0.1/captcha_font_sdk/group_id.py +21 -0
  16. captcha_font_sdk-0.0.1/captcha_font_sdk/local_restore_fs.py +29 -0
  17. captcha_font_sdk-0.0.1/captcha_font_sdk/local_restore_paths.py +43 -0
  18. captcha_font_sdk-0.0.1/captcha_font_sdk/local_restore_runner.py +132 -0
  19. captcha_font_sdk-0.0.1/captcha_font_sdk/local_restore_types.py +81 -0
  20. captcha_font_sdk-0.0.1/captcha_font_sdk/local_restore_vote.py +116 -0
  21. captcha_font_sdk-0.0.1/captcha_font_sdk/locator.py +464 -0
  22. captcha_font_sdk-0.0.1/captcha_font_sdk/mask_component_extractor.py +68 -0
  23. captcha_font_sdk-0.0.1/captcha_font_sdk/recognizer.py +418 -0
  24. captcha_font_sdk-0.0.1/captcha_font_sdk/slider_locator.py +108 -0
  25. captcha_font_sdk-0.0.1/captcha_font_sdk/text_layer_renderer.py +70 -0
  26. captcha_font_sdk-0.0.1/captcha_font_sdk/text_region_builder.py +138 -0
  27. captcha_font_sdk-0.0.1/captcha_font_sdk/types.py +214 -0
  28. captcha_font_sdk-0.0.1/captcha_font_sdk.egg-info/PKG-INFO +261 -0
  29. captcha_font_sdk-0.0.1/captcha_font_sdk.egg-info/SOURCES.txt +32 -0
  30. captcha_font_sdk-0.0.1/captcha_font_sdk.egg-info/dependency_links.txt +1 -0
  31. captcha_font_sdk-0.0.1/captcha_font_sdk.egg-info/requires.txt +1 -0
  32. captcha_font_sdk-0.0.1/captcha_font_sdk.egg-info/top_level.txt +1 -0
  33. captcha_font_sdk-0.0.1/pyproject.toml +46 -0
  34. captcha_font_sdk-0.0.1/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 JSREI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: captcha-font-sdk
3
+ Version: 0.0.1
4
+ Summary: Captcha recognizer SDK for background mapping, font localization, slider gap detection, and local background restore.
5
+ Author: JSREI
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/JSREI/force-fuck-captcha-background
8
+ Project-URL: Repository, https://github.com/JSREI/force-fuck-captcha-background
9
+ Project-URL: Issues, https://github.com/JSREI/force-fuck-captcha-background/issues
10
+ Keywords: captcha,image-processing,ocr,slider,font
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: Pillow>=10.0.0
26
+ Dynamic: license-file
27
+
28
+ # Captcha Recognizer SDK (Python)
29
+
30
+ ## Install
31
+
32
+ pip install captcha-font-sdk
33
+
34
+ From source:
35
+
36
+ cd sdk/python/captcha-font-sdk
37
+ pip install -e .
38
+
39
+ If you run scripts from repo root, add SDK dir to `PYTHONPATH`:
40
+
41
+ ```bash
42
+ PYTHONPATH=sdk/python/captcha-font-sdk python sdk/python/captcha-font-sdk/examples/demo.py
43
+ ```
44
+
45
+ ## What this SDK does
46
+
47
+ 1. Build `group_id -> full background image` mapping from your background directory.
48
+ 2. For a new captcha image:
49
+ - compute `group_id` from 4 corner RGB pixels,
50
+ - find matching full background,
51
+ - diff captcha vs background.
52
+ 3. Provide **different APIs by captcha type**:
53
+ - font captcha: extract color-based connected components (字体验证码),
54
+ - slider captcha: locate the largest gap region and return bbox/center (缺口验证码).
55
+ 4. For font captcha, provide:
56
+ - text position detection (文字位置框),
57
+ - text-pixel extraction to transparent PNG (文字像素扣图).
58
+ 5. Provide local restore workflow (same capability as UI):
59
+ - recursive scan captcha directory,
60
+ - group by 4-corner bucket id,
61
+ - per-pixel voting to restore backgrounds,
62
+ - write background images + `summary.json`.
63
+
64
+ ## group_id format
65
+
66
+ `{width}x{height}|lt_r,lt_g,lt_b|rt_r,rt_g,rt_b|lb_r,lb_g,lb_b|rb_r,rb_g,rb_b`
67
+
68
+ ## API overview
69
+
70
+ ### 1) Unified facade: `CaptchaRecognizer(...)`
71
+
72
+ - `build_background_index(background_dir, recursive=True, exts=None)`
73
+ - `recognize_font(captcha_path, include_pixels=True)`
74
+ - `recognize_text_positions(captcha_path, include_pixels=True)`
75
+ - `extract_text_layer(captcha_path, output_path=None, crop_to_content=False)`
76
+ - `restore_background_by_captcha(captcha_path, output_path=None)`
77
+ - `extract_font_glyphs(captcha_path, include_pixels=True, include_rgba_2d=False)`
78
+ - `extract_font_glyph_features(captcha_path, target_width=32, target_height=32, keep_aspect_ratio=True)`
79
+ - `extract_font_glyph_slots(captcha_path, slot_count=5, target_width=32, target_height=32, ...)`
80
+ - `export_font_glyph_images(captcha_path, output_dir, file_prefix=None)`
81
+ - `batch_extract_font_glyph_features(input_dir, target_width=32, target_height=32, ...)`
82
+ - `export_font_glyph_dataset_npz(input_dir, output_npz_path, target_width=32, target_height=32, ...)`
83
+ - `recognize_slider(captcha_path)`
84
+ - `run_local_restore(input_dir, output_dir, clear_output_before_run=False, recursive=True, max_error_items=200, progress_callback=None, stop_checker=None)`
85
+ - `recognize(captcha_path, captcha_type="font" | "slider", include_pixels=True)`
86
+
87
+ ### 2) Font captcha API: `CaptchaFontLocator(...)`
88
+
89
+ - `locate_fonts(...)`
90
+ - `locate_fonts_dict(...)`
91
+ - `restore_background_by_captcha(...)`
92
+ - `restore_background_by_captcha_dict(...)`
93
+ - `extract_font_glyphs(...)`
94
+ - `extract_font_glyphs_dict(...)`
95
+ - `extract_font_glyph_features(...)`
96
+ - `extract_font_glyph_features_dict(...)`
97
+ - `extract_font_glyph_slots(...)`
98
+ - `extract_font_glyph_slots_dict(...)`
99
+ - `export_font_glyph_images(...)`
100
+ - `export_font_glyph_images_dict(...)`
101
+ - `locate_text_positions(...)`
102
+ - `locate_text_positions_dict(...)`
103
+ - `extract_text_layer(...)`
104
+ - `extract_text_layer_dict(...)`
105
+
106
+ ### 3) Slider captcha API: `CaptchaSliderLocator(...)`
107
+
108
+ - `locate_gap(...)`
109
+ - `locate_gap_dict(...)`
110
+
111
+ ### 4) Local restore API (UI parity)
112
+
113
+ - `CaptchaRecognizer.run_local_restore(...)`
114
+ - `CaptchaRecognizer.run_local_restore_dict(...)`
115
+
116
+ ## Quick examples
117
+
118
+ ### Unified API
119
+
120
+ ```python
121
+ from captcha_font_sdk import CaptchaRecognizer
122
+
123
+ sdk = CaptchaRecognizer(
124
+ diff_threshold=18,
125
+ font_min_component_pixels=8,
126
+ slider_min_gap_pixels=20,
127
+ connectivity=8,
128
+ )
129
+ sdk.build_background_index("/your/full-background-dir")
130
+
131
+ font_result = sdk.recognize_font_dict("/your/new-font-captcha.png", include_pixels=True)
132
+ text_positions = sdk.recognize_text_positions_dict("/your/new-font-captcha.png", include_pixels=True)
133
+ text_layer = sdk.extract_text_layer_dict(
134
+ "/your/new-font-captcha.png",
135
+ output_path="./text_layer.png",
136
+ crop_to_content=False,
137
+ )
138
+ restored_bg = sdk.restore_background_by_captcha_dict(
139
+ "/your/new-font-captcha.png",
140
+ output_path="./restored_background.png",
141
+ )
142
+ glyphs = sdk.extract_font_glyphs_dict(
143
+ "/your/new-font-captcha.png",
144
+ include_pixels=True,
145
+ include_rgba_2d=False,
146
+ )
147
+ glyph_features = sdk.extract_font_glyph_features_dict(
148
+ "/your/new-font-captcha.png",
149
+ target_width=32,
150
+ target_height=32,
151
+ )
152
+ glyph_slots = sdk.extract_font_glyph_slots_dict(
153
+ "/your/new-font-captcha.png",
154
+ slot_count=5,
155
+ target_width=32,
156
+ target_height=32,
157
+ )
158
+ glyph_images = sdk.export_font_glyph_images_dict(
159
+ "/your/new-font-captcha.png",
160
+ output_dir="./glyph_images",
161
+ )
162
+ slider_result = sdk.recognize_slider_dict("/your/new-slider-captcha.png")
163
+
164
+ print(font_result["stats"]["component_count"])
165
+ print(text_positions["stats"]["region_count"])
166
+ print(text_layer["output_path"])
167
+ print(restored_bg["background_path"], restored_bg["output_path"])
168
+ print(glyphs["stats"]["glyph_count"])
169
+ print(glyphs["glyphs"][0]["rect_index"], glyphs["glyphs"][0]["bbox"])
170
+ print(glyphs["glyphs"][0]["bitmap_2d"])
171
+ print(len(glyph_features["glyph_features"][0]["vector_1d"])) # 32*32
172
+ print(glyph_slots["stats"]["filled_slots"], glyph_slots["slot_count"])
173
+ print(glyph_images["stats"]["exported_count"], glyph_images["output_dir"])
174
+ print(slider_result["gap"])
175
+ ```
176
+
177
+ ### Batch glyph feature extraction
178
+
179
+ ```python
180
+ batch = sdk.batch_extract_font_glyph_features_dict(
181
+ input_dir="/path/to/captcha_dir",
182
+ target_width=32,
183
+ target_height=32,
184
+ recursive=True,
185
+ include_payload=False,
186
+ output_json_path="./glyph_batch_summary.json",
187
+ )
188
+ print(batch["success_count"], batch["error_count"])
189
+ ```
190
+
191
+ ### Export training dataset (`npz`)
192
+
193
+ ```python
194
+ dataset = sdk.export_font_glyph_dataset_npz_dict(
195
+ input_dir="/path/to/captcha_dir",
196
+ output_npz_path="./glyph_dataset.npz",
197
+ target_width=32,
198
+ target_height=32,
199
+ recursive=True,
200
+ output_json_path="./glyph_dataset_meta.json",
201
+ )
202
+ print(dataset["glyph_sample_count"], dataset["output_npz_path"])
203
+ ```
204
+
205
+ ### Local restore (same as UI "本地还原")
206
+
207
+ ```python
208
+ from captcha_font_sdk import CaptchaRecognizer
209
+
210
+ sdk = CaptchaRecognizer()
211
+ summary = sdk.run_local_restore_dict(
212
+ input_dir="/path/to/captcha_images",
213
+ output_dir="/path/to/output_backgrounds",
214
+ clear_output_before_run=True,
215
+ recursive=True,
216
+ max_error_items=200,
217
+ )
218
+
219
+ print(summary["bucket_count"], summary["output_files"])
220
+ print(summary["summary_path"])
221
+ ```
222
+
223
+ `run_local_restore(...)` will also refresh SDK background index from `output_dir`,
224
+ so you can call `recognize_*` APIs immediately after local restore.
225
+
226
+ ### Separate APIs by captcha type
227
+
228
+ ```python
229
+ from captcha_font_sdk import CaptchaFontLocator, CaptchaSliderLocator
230
+
231
+ background_dir = "/your/full-background-dir"
232
+
233
+ font_sdk = CaptchaFontLocator()
234
+ font_sdk.build_background_index(background_dir)
235
+ font_result = font_sdk.locate_fonts_dict("/your/new-font-captcha.png")
236
+
237
+ slider_sdk = CaptchaSliderLocator()
238
+ slider_sdk.build_background_index(background_dir)
239
+ slider_result = slider_sdk.locate_gap_dict("/your/new-slider-captcha.png")
240
+
241
+ text_positions = font_sdk.locate_text_positions_dict("/your/new-font-captcha.png", include_pixels=True)
242
+ text_layer = font_sdk.extract_text_layer_dict(
243
+ "/your/new-font-captcha.png",
244
+ output_path="./text_layer.png",
245
+ crop_to_content=True,
246
+ )
247
+ ```
248
+
249
+ ## Notes
250
+
251
+ - If captcha size and matched background size differ, SDK raises an error.
252
+ - If `group_id` is not found, SDK raises `KeyError`.
253
+ - Slider result `gap` may be `null` when no connected region reaches `min_gap_pixels`.
254
+ - Text position detection uses exact-color connected components plus size/fill filtering and near-neighbor merge.
255
+ - `extract_text_layer(...)` returns transparent image with only text pixels preserved.
256
+ - `export_font_glyph_dataset_npz(...)` requires `numpy` (`pip install numpy`).
257
+ - You can tune:
258
+ - increase `diff_threshold` to reduce tiny color jitter,
259
+ - increase `font_min_component_pixels` / `min_component_pixels` to suppress font noise,
260
+ - increase `slider_min_gap_pixels` / `min_gap_pixels` to suppress slider noise,
261
+ - tune `text_min_width / text_min_height / text_min_fill_ratio / text_max_fill_ratio` for text boxes.
@@ -0,0 +1,234 @@
1
+ # Captcha Recognizer SDK (Python)
2
+
3
+ ## Install
4
+
5
+ pip install captcha-font-sdk
6
+
7
+ From source:
8
+
9
+ cd sdk/python/captcha-font-sdk
10
+ pip install -e .
11
+
12
+ If you run scripts from repo root, add SDK dir to `PYTHONPATH`:
13
+
14
+ ```bash
15
+ PYTHONPATH=sdk/python/captcha-font-sdk python sdk/python/captcha-font-sdk/examples/demo.py
16
+ ```
17
+
18
+ ## What this SDK does
19
+
20
+ 1. Build `group_id -> full background image` mapping from your background directory.
21
+ 2. For a new captcha image:
22
+ - compute `group_id` from 4 corner RGB pixels,
23
+ - find matching full background,
24
+ - diff captcha vs background.
25
+ 3. Provide **different APIs by captcha type**:
26
+ - font captcha: extract color-based connected components (字体验证码),
27
+ - slider captcha: locate the largest gap region and return bbox/center (缺口验证码).
28
+ 4. For font captcha, provide:
29
+ - text position detection (文字位置框),
30
+ - text-pixel extraction to transparent PNG (文字像素扣图).
31
+ 5. Provide local restore workflow (same capability as UI):
32
+ - recursive scan captcha directory,
33
+ - group by 4-corner bucket id,
34
+ - per-pixel voting to restore backgrounds,
35
+ - write background images + `summary.json`.
36
+
37
+ ## group_id format
38
+
39
+ `{width}x{height}|lt_r,lt_g,lt_b|rt_r,rt_g,rt_b|lb_r,lb_g,lb_b|rb_r,rb_g,rb_b`
40
+
41
+ ## API overview
42
+
43
+ ### 1) Unified facade: `CaptchaRecognizer(...)`
44
+
45
+ - `build_background_index(background_dir, recursive=True, exts=None)`
46
+ - `recognize_font(captcha_path, include_pixels=True)`
47
+ - `recognize_text_positions(captcha_path, include_pixels=True)`
48
+ - `extract_text_layer(captcha_path, output_path=None, crop_to_content=False)`
49
+ - `restore_background_by_captcha(captcha_path, output_path=None)`
50
+ - `extract_font_glyphs(captcha_path, include_pixels=True, include_rgba_2d=False)`
51
+ - `extract_font_glyph_features(captcha_path, target_width=32, target_height=32, keep_aspect_ratio=True)`
52
+ - `extract_font_glyph_slots(captcha_path, slot_count=5, target_width=32, target_height=32, ...)`
53
+ - `export_font_glyph_images(captcha_path, output_dir, file_prefix=None)`
54
+ - `batch_extract_font_glyph_features(input_dir, target_width=32, target_height=32, ...)`
55
+ - `export_font_glyph_dataset_npz(input_dir, output_npz_path, target_width=32, target_height=32, ...)`
56
+ - `recognize_slider(captcha_path)`
57
+ - `run_local_restore(input_dir, output_dir, clear_output_before_run=False, recursive=True, max_error_items=200, progress_callback=None, stop_checker=None)`
58
+ - `recognize(captcha_path, captcha_type="font" | "slider", include_pixels=True)`
59
+
60
+ ### 2) Font captcha API: `CaptchaFontLocator(...)`
61
+
62
+ - `locate_fonts(...)`
63
+ - `locate_fonts_dict(...)`
64
+ - `restore_background_by_captcha(...)`
65
+ - `restore_background_by_captcha_dict(...)`
66
+ - `extract_font_glyphs(...)`
67
+ - `extract_font_glyphs_dict(...)`
68
+ - `extract_font_glyph_features(...)`
69
+ - `extract_font_glyph_features_dict(...)`
70
+ - `extract_font_glyph_slots(...)`
71
+ - `extract_font_glyph_slots_dict(...)`
72
+ - `export_font_glyph_images(...)`
73
+ - `export_font_glyph_images_dict(...)`
74
+ - `locate_text_positions(...)`
75
+ - `locate_text_positions_dict(...)`
76
+ - `extract_text_layer(...)`
77
+ - `extract_text_layer_dict(...)`
78
+
79
+ ### 3) Slider captcha API: `CaptchaSliderLocator(...)`
80
+
81
+ - `locate_gap(...)`
82
+ - `locate_gap_dict(...)`
83
+
84
+ ### 4) Local restore API (UI parity)
85
+
86
+ - `CaptchaRecognizer.run_local_restore(...)`
87
+ - `CaptchaRecognizer.run_local_restore_dict(...)`
88
+
89
+ ## Quick examples
90
+
91
+ ### Unified API
92
+
93
+ ```python
94
+ from captcha_font_sdk import CaptchaRecognizer
95
+
96
+ sdk = CaptchaRecognizer(
97
+ diff_threshold=18,
98
+ font_min_component_pixels=8,
99
+ slider_min_gap_pixels=20,
100
+ connectivity=8,
101
+ )
102
+ sdk.build_background_index("/your/full-background-dir")
103
+
104
+ font_result = sdk.recognize_font_dict("/your/new-font-captcha.png", include_pixels=True)
105
+ text_positions = sdk.recognize_text_positions_dict("/your/new-font-captcha.png", include_pixels=True)
106
+ text_layer = sdk.extract_text_layer_dict(
107
+ "/your/new-font-captcha.png",
108
+ output_path="./text_layer.png",
109
+ crop_to_content=False,
110
+ )
111
+ restored_bg = sdk.restore_background_by_captcha_dict(
112
+ "/your/new-font-captcha.png",
113
+ output_path="./restored_background.png",
114
+ )
115
+ glyphs = sdk.extract_font_glyphs_dict(
116
+ "/your/new-font-captcha.png",
117
+ include_pixels=True,
118
+ include_rgba_2d=False,
119
+ )
120
+ glyph_features = sdk.extract_font_glyph_features_dict(
121
+ "/your/new-font-captcha.png",
122
+ target_width=32,
123
+ target_height=32,
124
+ )
125
+ glyph_slots = sdk.extract_font_glyph_slots_dict(
126
+ "/your/new-font-captcha.png",
127
+ slot_count=5,
128
+ target_width=32,
129
+ target_height=32,
130
+ )
131
+ glyph_images = sdk.export_font_glyph_images_dict(
132
+ "/your/new-font-captcha.png",
133
+ output_dir="./glyph_images",
134
+ )
135
+ slider_result = sdk.recognize_slider_dict("/your/new-slider-captcha.png")
136
+
137
+ print(font_result["stats"]["component_count"])
138
+ print(text_positions["stats"]["region_count"])
139
+ print(text_layer["output_path"])
140
+ print(restored_bg["background_path"], restored_bg["output_path"])
141
+ print(glyphs["stats"]["glyph_count"])
142
+ print(glyphs["glyphs"][0]["rect_index"], glyphs["glyphs"][0]["bbox"])
143
+ print(glyphs["glyphs"][0]["bitmap_2d"])
144
+ print(len(glyph_features["glyph_features"][0]["vector_1d"])) # 32*32
145
+ print(glyph_slots["stats"]["filled_slots"], glyph_slots["slot_count"])
146
+ print(glyph_images["stats"]["exported_count"], glyph_images["output_dir"])
147
+ print(slider_result["gap"])
148
+ ```
149
+
150
+ ### Batch glyph feature extraction
151
+
152
+ ```python
153
+ batch = sdk.batch_extract_font_glyph_features_dict(
154
+ input_dir="/path/to/captcha_dir",
155
+ target_width=32,
156
+ target_height=32,
157
+ recursive=True,
158
+ include_payload=False,
159
+ output_json_path="./glyph_batch_summary.json",
160
+ )
161
+ print(batch["success_count"], batch["error_count"])
162
+ ```
163
+
164
+ ### Export training dataset (`npz`)
165
+
166
+ ```python
167
+ dataset = sdk.export_font_glyph_dataset_npz_dict(
168
+ input_dir="/path/to/captcha_dir",
169
+ output_npz_path="./glyph_dataset.npz",
170
+ target_width=32,
171
+ target_height=32,
172
+ recursive=True,
173
+ output_json_path="./glyph_dataset_meta.json",
174
+ )
175
+ print(dataset["glyph_sample_count"], dataset["output_npz_path"])
176
+ ```
177
+
178
+ ### Local restore (same as UI "本地还原")
179
+
180
+ ```python
181
+ from captcha_font_sdk import CaptchaRecognizer
182
+
183
+ sdk = CaptchaRecognizer()
184
+ summary = sdk.run_local_restore_dict(
185
+ input_dir="/path/to/captcha_images",
186
+ output_dir="/path/to/output_backgrounds",
187
+ clear_output_before_run=True,
188
+ recursive=True,
189
+ max_error_items=200,
190
+ )
191
+
192
+ print(summary["bucket_count"], summary["output_files"])
193
+ print(summary["summary_path"])
194
+ ```
195
+
196
+ `run_local_restore(...)` will also refresh SDK background index from `output_dir`,
197
+ so you can call `recognize_*` APIs immediately after local restore.
198
+
199
+ ### Separate APIs by captcha type
200
+
201
+ ```python
202
+ from captcha_font_sdk import CaptchaFontLocator, CaptchaSliderLocator
203
+
204
+ background_dir = "/your/full-background-dir"
205
+
206
+ font_sdk = CaptchaFontLocator()
207
+ font_sdk.build_background_index(background_dir)
208
+ font_result = font_sdk.locate_fonts_dict("/your/new-font-captcha.png")
209
+
210
+ slider_sdk = CaptchaSliderLocator()
211
+ slider_sdk.build_background_index(background_dir)
212
+ slider_result = slider_sdk.locate_gap_dict("/your/new-slider-captcha.png")
213
+
214
+ text_positions = font_sdk.locate_text_positions_dict("/your/new-font-captcha.png", include_pixels=True)
215
+ text_layer = font_sdk.extract_text_layer_dict(
216
+ "/your/new-font-captcha.png",
217
+ output_path="./text_layer.png",
218
+ crop_to_content=True,
219
+ )
220
+ ```
221
+
222
+ ## Notes
223
+
224
+ - If captcha size and matched background size differ, SDK raises an error.
225
+ - If `group_id` is not found, SDK raises `KeyError`.
226
+ - Slider result `gap` may be `null` when no connected region reaches `min_gap_pixels`.
227
+ - Text position detection uses exact-color connected components plus size/fill filtering and near-neighbor merge.
228
+ - `extract_text_layer(...)` returns transparent image with only text pixels preserved.
229
+ - `export_font_glyph_dataset_npz(...)` requires `numpy` (`pip install numpy`).
230
+ - You can tune:
231
+ - increase `diff_threshold` to reduce tiny color jitter,
232
+ - increase `font_min_component_pixels` / `min_component_pixels` to suppress font noise,
233
+ - increase `slider_min_gap_pixels` / `min_gap_pixels` to suppress slider noise,
234
+ - tune `text_min_width / text_min_height / text_min_fill_ratio / text_max_fill_ratio` for text boxes.
@@ -0,0 +1,72 @@
1
+ __version__ = "0.0.1"
2
+
3
+ from .locator import CaptchaFontLocator
4
+ from .local_restore_types import (
5
+ BucketSummary,
6
+ LocalRestoreConfig,
7
+ LocalRestoreStatus,
8
+ LocalRestoreSummary,
9
+ ProgressCallback,
10
+ ProcessingErrorItem,
11
+ StopChecker,
12
+ )
13
+ from .recognizer import CaptchaRecognizer
14
+ from .slider_locator import CaptchaSliderLocator
15
+ from .types import (
16
+ BackgroundRestoreResult,
17
+ BackgroundMeta,
18
+ BatchGlyphExtractItem,
19
+ BatchGlyphExtractResult,
20
+ CaptchaType,
21
+ FontGlyph,
22
+ FontGlyphImageExportResult,
23
+ FontGlyphImageItem,
24
+ FontGlyphFeature,
25
+ FontGlyphFeatureExtractResult,
26
+ FontGlyphExtractResult,
27
+ FontGlyphSlot,
28
+ FontGlyphSlotExtractResult,
29
+ GlyphDatasetExportResult,
30
+ FontComponent,
31
+ LocateResult,
32
+ SliderGap,
33
+ SliderLocateResult,
34
+ TextLayerResult,
35
+ TextLocateResult,
36
+ TextRegion,
37
+ )
38
+
39
+ __all__ = [
40
+ "__version__",
41
+ "CaptchaRecognizer",
42
+ "CaptchaFontLocator",
43
+ "CaptchaSliderLocator",
44
+ "BackgroundRestoreResult",
45
+ "BackgroundMeta",
46
+ "CaptchaType",
47
+ "BatchGlyphExtractItem",
48
+ "BatchGlyphExtractResult",
49
+ "FontGlyph",
50
+ "FontGlyphImageExportResult",
51
+ "FontGlyphImageItem",
52
+ "FontGlyphFeature",
53
+ "FontGlyphFeatureExtractResult",
54
+ "FontGlyphExtractResult",
55
+ "FontGlyphSlot",
56
+ "FontGlyphSlotExtractResult",
57
+ "GlyphDatasetExportResult",
58
+ "LocalRestoreConfig",
59
+ "LocalRestoreStatus",
60
+ "LocalRestoreSummary",
61
+ "BucketSummary",
62
+ "ProgressCallback",
63
+ "StopChecker",
64
+ "ProcessingErrorItem",
65
+ "FontComponent",
66
+ "LocateResult",
67
+ "SliderGap",
68
+ "SliderLocateResult",
69
+ "TextRegion",
70
+ "TextLocateResult",
71
+ "TextLayerResult",
72
+ ]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Dict, Iterable, List, Optional, Tuple
5
+
6
+ from PIL import Image
7
+
8
+ from .group_id import group_id_from_pixels
9
+ from .types import BackgroundMeta
10
+
11
+
12
+ _DEFAULT_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp"}
13
+
14
+
15
+ def load_rgba_pixels(image_path: str) -> Tuple[int, int, List[Tuple[int, ...]]]:
16
+ with Image.open(image_path) as im:
17
+ rgba = im.convert("RGBA")
18
+ width, height = rgba.size
19
+ return width, height, list(rgba.getdata())
20
+
21
+
22
+ def compute_group_id(image_path: str) -> str:
23
+ width, height, pixels = load_rgba_pixels(image_path)
24
+ return group_id_from_pixels(pixels, width, height)
25
+
26
+
27
+ def build_background_index(
28
+ background_dir: str,
29
+ recursive: bool = True,
30
+ exts: Optional[Iterable[str]] = None,
31
+ ) -> Dict[str, BackgroundMeta]:
32
+ root = Path(background_dir)
33
+ if not root.exists() or not root.is_dir():
34
+ raise FileNotFoundError(f"background_dir not found: {background_dir}")
35
+
36
+ valid_exts = _DEFAULT_IMAGE_EXTS if exts is None else {e.lower() for e in exts}
37
+ files = root.rglob("*") if recursive else root.glob("*")
38
+
39
+ index: Dict[str, BackgroundMeta] = {}
40
+ for file_path in files:
41
+ if not file_path.is_file() or file_path.suffix.lower() not in valid_exts:
42
+ continue
43
+ width, height, pixels = load_rgba_pixels(str(file_path))
44
+ group_id = group_id_from_pixels(pixels, width, height)
45
+ if group_id not in index:
46
+ index[group_id] = BackgroundMeta(
47
+ group_id=group_id,
48
+ image_path=str(file_path),
49
+ width=width,
50
+ height=height,
51
+ )
52
+ return index
53
+
54
+
55
+ def build_background_index_from_files(
56
+ image_paths: Iterable[str],
57
+ exts: Optional[Iterable[str]] = None,
58
+ ) -> Dict[str, BackgroundMeta]:
59
+ valid_exts = _DEFAULT_IMAGE_EXTS if exts is None else {e.lower() for e in exts}
60
+ index: Dict[str, BackgroundMeta] = {}
61
+ for image_path in image_paths:
62
+ file_path = Path(image_path)
63
+ if not file_path.is_file() or file_path.suffix.lower() not in valid_exts:
64
+ continue
65
+ width, height, pixels = load_rgba_pixels(str(file_path))
66
+ group_id = group_id_from_pixels(pixels, width, height)
67
+ if group_id not in index:
68
+ index[group_id] = BackgroundMeta(
69
+ group_id=group_id,
70
+ image_path=str(file_path),
71
+ width=width,
72
+ height=height,
73
+ )
74
+ return index