Download example_usage.py from fang718/pp-ocrv6-hanzi-unicode-ocr: direct link, hf CLI and curl.
- Browser
- Download file 3.84 kB
-
https://huggingface.co/fang718/pp-ocrv6-hanzi-unicode-ocr/resolve/main/example_usage.py
- Command line
-
hf download hf://fang718/pp-ocrv6-hanzi-unicode-ocr/example_usage.py
-
curl -L -o example_usage.py https://huggingface.co/fang718/pp-ocrv6-hanzi-unicode-ocr/resolve/main/example_usage.py
3.84 kB
| """Minimal end-to-end use of the PP-OCRv6 Hanzi Unicode OCR model. | |
| python example_usage.py path/to/glyph.png | |
| With no argument the script renders a few characters from a local font and | |
| recognises them, so it can be pasted into a fresh environment as a smoke test. | |
| """ | |
| import json | |
| import math | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import onnxruntime as ort | |
| from PIL import Image, ImageDraw, ImageFont | |
| MODEL_DIR = Path(__file__).resolve().parent | |
| # Default fonts used only by the self-test path. Any TTF/OTF works. | |
| FONT_MAIN = "C:/Windows/Fonts/simsun.ttc" | |
| FONT_EXT_B = "C:/Windows/Fonts/simsunb.ttf" | |
| class UnicodeOCR: | |
| def __init__(self, model_dir=MODEL_DIR, providers=None): | |
| model_dir = Path(model_dir) | |
| meta = json.loads((model_dir / "metadata.json").read_text(encoding="utf-8")) | |
| self.vocab = meta["vocab"] | |
| self.channels, self.height, self.width = meta["image_shape"] | |
| self.session = ort.InferenceSession( | |
| str(model_dir / "model.onnx"), | |
| providers=providers or ["CPUExecutionProvider"], | |
| ) | |
| self.input_name = self.session.get_inputs()[0].name | |
| def prepare(self, image): | |
| """RGB -> BGR, aspect-preserving resize to height 48, [-1, 1], right pad.""" | |
| image = image.convert("RGB") | |
| inner_w = min(math.ceil(self.height * image.width / image.height), self.width) | |
| bgr = np.asarray(image)[:, :, ::-1] | |
| resized = Image.fromarray(bgr).resize((inner_w, self.height), Image.BILINEAR) | |
| plane = np.asarray(resized).astype(np.float32).transpose(2, 0, 1) | |
| plane = plane / 127.5 - 1.0 | |
| canvas = np.zeros((self.channels, self.height, self.width), np.float32) | |
| canvas[:, :, :inner_w] = plane | |
| return canvas[None] | |
| def recognise(self, image): | |
| """Return (text, mean_score) for one single-glyph image.""" | |
| probs = self.session.run(None, {self.input_name: self.prepare(image)})[0][0] | |
| index = probs.argmax(-1) | |
| scores = probs.max(-1) | |
| text, kept, last = [], [], None | |
| for i, score in zip(index, scores): | |
| if i != last and i != 0: | |
| text.append(self.vocab[i]) | |
| kept.append(float(score)) | |
| last = i | |
| return "".join(text), (sum(kept) / len(kept) if kept else 0.0) | |
| def render_glyph(char, font_path, px=200): | |
| font = ImageFont.truetype(font_path, px) | |
| left, top, right, bottom = font.getbbox(char) | |
| image = Image.new("L", (right - left + 20, bottom - top + 20), 255) | |
| ImageDraw.Draw(image).text((10 - left, 10 - top), char, font=font, fill=0) | |
| return image | |
| def pick_font(char): | |
| # Characters above the BMP need a font that actually contains them, otherwise | |
| # the renderer substitutes .notdef and the model is asked to read a box. | |
| return FONT_EXT_B if ord(char) >= 0x20000 else FONT_MAIN | |
| def self_test(model): | |
| chars = ["\u4e00", "\u54c1", "\u5b57", "\u660e", "\u6728", "\u6797", "\u68ee", | |
| "\u4dbf", "\u9fff", "\U00020000", "\U0002a6d6", "\U0002a6df"] | |
| hits = 0 | |
| print(f"{'char':<6}{'codepoint':<12}{'predicted':<10}{'score':<10}ok") | |
| for char in chars: | |
| predicted, score = model.recognise(render_glyph(char, pick_font(char))) | |
| hit = predicted == char | |
| hits += hit | |
| print(f"{char:<6}{'U+%X' % ord(char):<12}{predicted:<10}{score:<10.4f}{hit}") | |
| print(f"\nmatched {hits}/{len(chars)}") | |
| return hits == len(chars) | |
| def main(): | |
| model = UnicodeOCR() | |
| if len(sys.argv) > 1: | |
| path = Path(sys.argv[1]) | |
| text, score = model.recognise(Image.open(path)) | |
| print(json.dumps({"file": str(path), "text": text, "score": score}, | |
| ensure_ascii=False)) | |
| return 0 | |
| return 0 if self_test(model) else 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |