生成 AI が出したレポート用の画像や看板の文字が 1 字だけ壊れている —— そういうとき、画像を作り直さずに、本当はこう書いてあるべき文字列を渡して直せます。入口は JSON 一枚(spec = {"items": [{"text": "電気設備", "bbox": [x, y, w, h]}]})で、Python の glyph_correct_spec からも MCP の fullseye_fix_text からも同じ形で呼べます。文字を認識はしません: 正しい文字列が与えられるので、各マスを指定の 1 字と 1 対 1 で照合するだけで済み、6,000 字の分類器は要りません。閾値は勘で置かず、その環境の書体で同じ字を描き分けた距離の 95 % 点(書体雑音の床)から導きます。
直し方は 2 つ。mode="repair_flagged"(既定)は床を超えた字だけ置き換えて正しい字には触らず、置換後に床より近くならなければ元に戻します(failed_verification)。mode="rewrite_line" は bbox の行を同じ書体で丸ごと描き直すので、見逃し・誤検出が結果に残らず字数の違いも直りますが、書体は変わります。看板の板は glyph_find_plate で起こせます(縁の直線 4 本の交点で四隅を取る)。行の位置(bbox)を人が測りたくなければ glyph_make_spec(rgb, texts) が暗い字の行を上から検出して指示書を作ります(MCP では items の bbox を省くだけ)。版面が取れなければ layout.status で断り、bbox の無い指示書では 1 画素も直しません。
報告は行ごとに status(ok / replaced / failed_verification / skipped / rewritten)、直せないときの reason と機械向けの reason_code(glyphops.REASON_CODES の鍵)、そして mismatch(typo = 誤字 / unrelated = 元の字が指示と無関係な疑い / none / unknown)を返します。描き直しは別物でも成功してしまうので、「指示か画像のどちらかが違う」を別枝で返します。
Fix the text inside an image against the string it should read, without regenerating the image: pass {"items": [{"text": "...", "bbox": [x, y, w, h]}]} to glyph_correct_spec (Python) or fullseye_fix_text (MCP). Nothing is recognised — the intended string is given, so each cell is verified one-to-one against one character, and the threshold comes from the typeface noise floor measured on the fonts of the environment. repair_flagged replaces only the glyphs above the floor and rolls back any replacement that does not verify; rewrite_line redraws the whole line in one typeface. The report carries a per-line status, a machine-readable reason_code for refusals, and mismatch (typo / unrelated) so that a caller notices when the original text has nothing to do with the intended one.
向く: 生成 AI の画像の誤字(レポートの図表の題、スライドの見出し、看板)。正しい文字列が分かっていて、行の位置(bbox)を与えられる場面。背景が単色で、文字が 1 色(縁取り・影・グラデが無い)。
向かない: ★文字認識(OCR)ではありません。何が書いてあるかは教えてもらう前提です。★行の位置は呼ぶ側が与えるか、glyph_make_spec の自動検出(暗い字 / 明るい地、CJK の等幅だけ。白抜き文字は取れない)に頼ります。bbox を外したときの見逃しは rewrite_line でも防げません。★縁取り・影・グラデーションの文字は色が多峰なので断ります(reason_code = multimodal_colour)。黙って塗りません。★字が小さい(マス幅 90 px 未満)と距離の順序が壊れるので、看板は先に板を起こして拡大してください。★mismatch の判定(壊れたマスの割合 2/3 以上かつ距離の中央値 0.10 以上で unrelated)は生成画像 26 枚と合成での実測から置いた目安で、保証ではありません —— 割合と距離そのもの(mismatch_fraction / mismatch_distance)を併記します。★書体は環境のものを使います(glyph_fonts())。CJK を描ける書体が無い環境では例外になります。
import fullseye as fs
import numpy as np
from PIL import Image
rgb = np.asarray(Image.open("report_figure.png").convert("RGB"), np.float64) / 255.0
spec = {"items": [{"text": "電気設備", "bbox": [24, 40, 384, 96]}], # 行に密着した箱
"policy": {"mode": "repair_flagged"}} # or "rewrite_line"
fixed, report = fs.glyph_correct_spec(rgb, spec)
for it in report["items"]:
print(it["status"], it["text"], it.get("mismatch"), it.get("reason_code", ""))
# replaced 電気設備 typo ← 壊れた 1 字だけ置き換わり、正しい字には触っていない
Image.fromarray((fixed * 255).round().astype("uint8")).save("report_figure_fixed.png")
glyph_correct_spec(入口)/ glyph_make_spec(bbox の自動検出)/ glyph_find_text_lines / glyph_rewrite_line / glyph_find_plate / glyph_typeface_noise_floor / glyph_rendering_noise_floor / glyph_distance / glyph_replace / glyph_split_cellsfullseye_fix_text(docs/MCP.md に呼び出し例)fix_text_in_image(2 つのモード・unrelated・断る例、前後の図)、poc_glyph_typo_detection(真値を植えて検出率と誤検出を数える)tests/test_glyphops.py(床・置換・巻き戻し・描き直し・誤字/別物・理由の語彙)、tests/test_mcp_server.py(fix_text の本体・入口の拒否・unrelated)