Decide text orientation per page instead of per line
RapidOCR's angle classifier judges each cut-out line on its own and now
and then turns a perfectly upright line upside down, which then reads as
garbage ("Jo a ds ne t i d s t d no" for a line ending in "or" on the
latest scan). It is off now. All lines on a page face the same way, so a
page that reads poorly (upright text scores 0.97-1.0 per character,
upside-down text 0.55-0.8) is read again rotated 180 degrees and the
better reading kept; that also covers pages turned 90 degrees. The app
shows an upside-down page turned the right way up.
Upright pages read about 25% faster without the per-line classifier.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-2
@@ -6,6 +6,7 @@ Latin script (English, Swedish, German, French, ...) as well as Chinese and Japa
|
||||
Independent of the scanner hardware, like myscan.stitching.
|
||||
|
||||
lines = Ocr()(bgr) # [Line(box, text, score, edges)], box = 4x2 corner points
|
||||
lines, flipped = Ocr().read(bgr) # ... and whether the page was upside down
|
||||
text, spans = layout(lines) # plain text; spans[i] = (start, end) of lines[i] in it
|
||||
joins = paragraph_joins(lines, text, spans)
|
||||
reflow(text, joins) # the text with paragraphs on one line each
|
||||
@@ -105,11 +106,25 @@ def char_edges(box, text, chars):
|
||||
return np.clip(np.maximum.accumulate(edges), 0.0, 1.0)
|
||||
|
||||
|
||||
def quality(lines):
|
||||
"""Mean confidence per character."""
|
||||
n = sum(len(ln.text) for ln in lines)
|
||||
return sum(ln.score * len(ln.text) for ln in lines) / n if n else 0.0
|
||||
|
||||
|
||||
class Ocr:
|
||||
"""Lazily loads the engine (about 0.3 s) on first use; calls are serialised."""
|
||||
"""Lazily loads the engine (about 0.3 s) on first use; calls are serialised.
|
||||
|
||||
RapidOCR's angle classifier, which flips each line it thinks is upside down, is off:
|
||||
it judges lines one by one and now and then turns a perfectly upright line over,
|
||||
reading it as garbage. All lines on a page face the same way, so the page is turned
|
||||
as a whole instead: upright text reads at 0.97-1.0 confidence and upside-down text
|
||||
at 0.55-0.8, so a page reading poorly is read again rotated 180 degrees, and the
|
||||
better reading kept. That also covers pages turned 90 degrees, whose lines are cut
|
||||
out turned one way, which is either right or upside down."""
|
||||
|
||||
def __init__(self, **params):
|
||||
self.params = {"Global.log_level": "warning", **params}
|
||||
self.params = {"Global.log_level": "warning", "Global.use_cls": False, **params}
|
||||
self._engine, self._lock = None, threading.Lock()
|
||||
|
||||
def engine(self):
|
||||
@@ -119,6 +134,19 @@ class Ocr:
|
||||
return self._engine
|
||||
|
||||
def __call__(self, bgr):
|
||||
return self.read(bgr)[0]
|
||||
|
||||
def read(self, bgr):
|
||||
"""(lines, flipped): flipped if the page read better rotated 180 degrees, in
|
||||
which case the boxes are in the coordinates of the rotated image."""
|
||||
lines = self.lines(bgr)
|
||||
if quality(lines) < 0.9:
|
||||
turned = self.lines(np.ascontiguousarray(bgr[::-1, ::-1]))
|
||||
if quality(turned) >= max(0.85, quality(lines)):
|
||||
return turned, True
|
||||
return lines, False
|
||||
|
||||
def lines(self, bgr):
|
||||
with self._lock:
|
||||
r = self.engine()(bgr, return_word_box=True, return_single_char_box=True)
|
||||
if r.boxes is None:
|
||||
|
||||
Reference in New Issue
Block a user