From 268c4a6e7eb94c38951e748948276eb6a2b9d4fa Mon Sep 17 00:00:00 2001 From: q Date: Sun, 8 Mar 2026 12:19:45 +0300 Subject: [PATCH] update --- README.md | 5 +- __pycache__/app.cpython-313.pyc | Bin 19394 -> 19880 bytes app.py | 9 ++ core/__pycache__/ocr.cpython-313.pyc | Bin 7070 -> 7092 bytes core/__pycache__/text_detect.cpython-313.pyc | Bin 6864 -> 14806 bytes core/text_detect.py | 157 ++++++++++++++++++- run_service.sh | 1 + 7 files changed, 163 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f3415c1..e386be9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Python 3.6-compatible Flask service that accepts an image and returns detected t ## Features - Accepts JPEG/PNG/WebP/BMP/TIFF/GIF (first frame) via multipart or base64 JSON. - Optional Google Coral EdgeTPU detection for scene context (loaded once at start). -- EAST text detector (OpenCV DNN) when model exists; otherwise whole-image fallback. +- EAST text detector (OpenCV DNN) when model exists; otherwise OpenCV contour-based text-region fallback. - OCR via `pytesseract` with per-block confidences; configurable languages. - Auto-selects `rus` or `eng` (via Tesseract OSD script detection) before full OCR when `OCR_LANG=eng+rus`. - If OCR result is empty, retries with fallback language/full-frame pass to improve recall. @@ -109,6 +109,7 @@ Example JSON response (fields may vary): "latency_ms": 123, "coral_used": true, "east_used": true, + "text_locator": "east", "ocr_lang": "eng", "ocr_lang_requested": "eng+rus", "ocr_lang_strategy": "auto_script", @@ -120,6 +121,6 @@ Example JSON response (fields may vary): ## Notes - If Coral is absent or fails, the service still returns OCR with `coral_used=false`. -- If EAST model is missing, the whole image is passed to OCR to avoid empty results. +- If EAST model is missing, service first tries OpenCV contour text regions and only then full-image OCR. - Keep images under `MAX_IMAGE_MB` (413 returned otherwise). - Optimize Tesseract languages to those you need to speed up OCR. diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index ac7b8debe10c0613342b55d28532ac03eea2787d..c0b5dabbc976890874478f3561aecc9edb603d55 100644 GIT binary patch delta 2408 zcmb7Edu)?c6u;m1wcXao`hLI1*t1=?(GA+I+lrHo@fhsmZjcI1>Dq0fd>!8g3=9tc zkf47k^jFJ=~tY?!HJk(&rg_ zO0=y=qQtrk{!gEiynV{W3`0Rj8L@wKuz=MFz-}6V{`X*B$^2#n&sl9d#p*a|cC|YQ zK**i8hq*P>oXqO|n*oSa17HIoPj;U*2=PP>I%Na2XBZ67YS5Yw04EDoWt3!Tk`44a z1wI^bU_CCg${JY1=5t=btOF91k&}k7qq!0CW~ADTv?3$-00R3YN>Lk;OI zPR^P+MQDB(fJ_GLv!_{^*6V0SSB%`_1lJl)3)QUNEyLlfp=OZ)^2}!JUjwzAic@Bzz^QX~s8P_Nb}G_U=2oE(T%|e)-1xa*3*Gn*W^_zw zV%a)oDeFKb-lgS5b(oAZhoouF%vo^@;>4R-z*+F$3e9N9>uD+GY$2S5xdBP9Wm=k%?0Tqx zYDhsdTUsH`dS=UfBF+vinF#QCp#|F6@?uOKa;EuN*L<&13)+cJXNE0y<1^3tG@`2jrmHY!j`x1d`1U@8imcTs>DJC`)905fv*UVxKhew zln>9wG!RR~qJyz+euiu!0v{9jm8gklBMy@=Q)o-Q4E5KW6{(!)Za%y{ImG`)6z`(Z z`WAr$i)gCeh?v<`C#ytwdiQ= zWmn+f+FKIM8|o2w+%&rDxP6iq*2;FW-(ubQ-sIf6;LZdyhYs87( zZG4SBB0&D8yTz802}9}4g0cx`^@ORA{IrErT8TsU<3@=wS1M60|nhM#a#v z;4;(0`x>CD{QXC{VH!|*%9K8swGcI%~*fUJ(@wHOFNdwMTOQ8jvZ_q-0?l)=$ zmM<7=&V@q~b0UbHhk=na18+nP9XITmasQ zp_ERE`Xl^6jOWG3wn`^-;l)$Px9XmK3DJ2D2hX1&pdvt#wLggT5Vf@27nPFvFQ~CK xNi!5W-D(t(((*3!ZL5_EjNNG+rj`9PWt?UJ?U|&Bct3#-tT9nr(8!vyzX8PE-jx6V delta 2004 zcmb7FS!^3c7@pm=H;#{7j-B|nQ|FH35S*lO?p{d~rKOonDP3sfCQei-8HcqaN-LW3 zfK(xINJF6rfrNPJLtm1#sKy3J-ru!hc_JMwn?d{Imm%X#EhMth6NF!`HPz(dfB%QXD=}ki_)(JE88>)f?S<)F6bNVrp3h8BWT5^ zAXN}y&)81(iN(!IVy5s^3L!|OpF92(LWv~wy>Y@^y*--`_Taj-gqD(G?2gzFg8C&7 zs9)%EL-`U?I)7{92tb{8%+AgS>>+ELknf9HAKZs&>H;J(jO}922JMRo`wWOvK+C8d z`>~(cNm$jy%x%?;Te_fG>Q<~;&w3NUFfi)Ihvd8zh$2vK`E90NFJJN``LY=X{AsL71_v^vQR&<|qg;yZ;xwiXYw zaMy0(CZk==!cXkYt}J_@`+#GJ`<(*F8Z!&?vq^OekNK{9LMA!f4)1fg!`|xgnbnJ2{s@45 z*yC}ipMjJum|RdN4yTT)>)iM=`=Mu02(aefDt4$wcAAgOC1#SXndH%o>VV&l-@qwt zUYhIOD~NYx=I-CeXn&Z!(w}0+fj-Nrhpv}9UVidS=*&JgGH{yR7`Pp-tpB>I;d-d` zx;xB2j_@73xy*dmiJVn;LZw#u%mH^yGtKC?Itl|C=67>l4U zVm{o0E?cbQ-RN?KW4yz3xrxhH+AX9QU5%J=M7rA1i0h=~IwQ!-E#~ocC)N2Qgh z5#*H)Blxd$TZvV=W)V0Rn~6`l<`n?>#pw%%A@^Ul^!kqdQi~T_R($~VVpg6{sOhAt z%CJrIdkzLw7re5Dxx^8w)`9vR78v?95a2;4z@nb#P{!d;4)-|RXOZ1&ay9q)kyQ_; z`u^hD7dc!3$QsquLMEwx$rYUOTw?W6zV&d(l{9tFrKT2Ulf7!^((>@4*fb*wu5}5C o(N&R)YX^;GLf2{oGFVR?Tb5j38BEXZT9uH&eSdY3ofvKS8$}y~-v9sr diff --git a/app.py b/app.py index fe043a8..7d9fd1a 100644 --- a/app.py +++ b/app.py @@ -279,6 +279,14 @@ def image2text(): coral_used = False boxes, east_used = txt_detector.detect(img_rgb) + h, w = img_rgb.shape[:2] + text_locator = "east" if east_used else "opencv_fallback" + if not east_used and len(boxes) == 1: + try: + if tuple([int(v) for v in boxes[0]]) == (0, 0, w, h): + text_locator = "full_image" + except Exception: + pass prep_options = { "upscale": OCR_UPSCALE, @@ -389,6 +397,7 @@ def image2text(): "latency_ms": latency_ms, "coral_used": coral_used, "east_used": east_used, + "text_locator": text_locator, "ocr_lang": ocr_lang_selected, "ocr_lang_requested": OCR_LANG, "ocr_lang_strategy": ocr_lang_strategy, diff --git a/core/__pycache__/ocr.cpython-313.pyc b/core/__pycache__/ocr.cpython-313.pyc index 3292b8bcf23be4da101ecee72d350a53ce2a9da9..88ef08c41f81892bd3c19b62b2bb3d5c63720cd0 100644 GIT binary patch delta 36 rcmbPdzQufl53{(Uenx(7s(ztJW@3C|rv7GM=0FJm=?V-( delta 14 VcmdmDKF@rE4>Ke8=49qT2>>QB1Wo_| diff --git a/core/__pycache__/text_detect.cpython-313.pyc b/core/__pycache__/text_detect.cpython-313.pyc index 641683aded463db4ea2410151b10729b9de0ef18..28a0e684b4f5674320c5ae854161ad2112eb82e9 100644 GIT binary patch literal 14806 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 delta 1364 zcmaJ>-A`L(7=PdM)s~)q9M0)VX|*k|!O4Jv76)vdFuMtpkg>BWOE;SXJ!?Bki|4eX z2{*X3cMW@`cXym@Lf^M!NO-UjDZyPprFs317UnSjay5C96Lvwkq`xd0-x zA6$SkxBC8hX@I1Gb3lg*&?yD#v|>hH=;L&A$|PGh&O!*q$`bWEBQ0mL*+9|I0?X-q zR_n_wswu6qVp^d^3l^dAcjqKS)MDT?KnJbuT=FOYF^I}t67pL4jySHPaTIC@Hmt{e z=+5ANS2Gr7LQtW@RM)P^g3V@Oa*V>{*f@tSx_szco68LqcJ_FfQaHt=;Oa-X9EU65 zQIHj(1Kqa;VZ#>CJx+96l$KEQh9U*PQVFv~6tTOZgjN|5rR{duQ2(r3;%48*VTq&) z)*;*;IXPVY%AM(3BzC)zVVFF9ShsKF{jKCy@n$?)k>za{Bss*)1l?L=F5}GCBliL4 zEQy^ZFiPMs`q6#T`xN0yXS7lwsui^PLSFArB(?c`Qk$+!wXH$)X~=Kt#iSFm1^pZv zd+`*>t!KFs38SDEGV=+wP|!1T#R9G*@gC3xLa`GdZ$ll(J`6X8*TEx-V_grO65I2z zH^6#-jY1#WjqVJ*JSbDs^&QKUULQmk=F6}~UsAIM`7;PKMEkL+6Ieu%!NZi?f_@qH z!k3VBq_d|U%M(Jgh?@DxVm?{SYDe`;I6#DqD7rZE 45.0: + continue + contour_area = float(cv2.contourArea(cnt)) + fill_ratio = contour_area / float(max(1, bw * bh)) + if fill_ratio < 0.03: + continue + pad_x = max(2, int(bw * 0.08)) + pad_y = max(2, int(bh * 0.30)) + x1 = max(0, x - pad_x) + y1 = max(0, y - pad_y) + x2 = min(image_w, x + bw + pad_x) + y2 = min(image_h, y + bh + pad_y) + out.append((_int0(x1), _int0(y1), _int0(x2), _int0(y2))) + return out + + def _normalize_boxes(self, boxes, image_w, image_h, max_boxes=16): + norm = [] + for box in boxes or []: + x1, y1, x2, y2 = _clip_box(box, image_w, image_h) + if x2 <= x1 or y2 <= y1: + continue + norm.append((x1, y1, x2, y2)) + if not norm: + return [] + kept = _nms_by_iou(norm, iou_thresh=0.45) + kept = sorted(kept, key=lambda b: ((b[3] - b[1]) * (b[2] - b[0])), reverse=True) + if max_boxes and len(kept) > max_boxes: + kept = kept[:max_boxes] + kept = sorted(kept, key=lambda b: (b[1], b[0])) + return kept + def _int0(val): try: return int(val) except Exception: return 0 + + +def _clip_box(box, image_w, image_h): + x1, y1, x2, y2 = box + x1 = max(0, min(_int0(x1), _int0(image_w))) + y1 = max(0, min(_int0(y1), _int0(image_h))) + x2 = max(0, min(_int0(x2), _int0(image_w))) + y2 = max(0, min(_int0(y2), _int0(image_h))) + return x1, y1, x2, y2 + + +def _nms_by_iou(boxes, iou_thresh=0.45): + if not boxes: + return [] + ranked = sorted(boxes, key=_box_area, reverse=True) + keep = [] + for candidate in ranked: + should_keep = True + for chosen in keep: + if _iou(candidate, chosen) >= iou_thresh: + should_keep = False + break + if should_keep: + keep.append(candidate) + return keep + + +def _box_area(box): + x1, y1, x2, y2 = box + return max(0, x2 - x1) * max(0, y2 - y1) + + +def _iou(a, b): + ax1, ay1, ax2, ay2 = a + bx1, by1, bx2, by2 = b + xx1 = max(ax1, bx1) + yy1 = max(ay1, by1) + xx2 = min(ax2, bx2) + yy2 = min(ay2, by2) + iw = max(0, xx2 - xx1) + ih = max(0, yy2 - yy1) + inter = iw * ih + if inter <= 0: + return 0.0 + union = float(_box_area(a) + _box_area(b) - inter) + if union <= 0: + return 0.0 + return inter / union diff --git a/run_service.sh b/run_service.sh index 4d6a5e2..af308cc 100755 --- a/run_service.sh +++ b/run_service.sh @@ -9,6 +9,7 @@ export MODEL_EDGETPU_PATH=${MODEL_EDGETPU_PATH:-./models/ssd_mobilenet_v2_coco_q export MODEL_LABELS_PATH=${MODEL_LABELS_PATH:-./models/coco_labels.txt} export MODEL_EAST_PATH=${MODEL_EAST_PATH:-./models/frozen_east_text_detection.pb} export OCR_LANG=${OCR_LANG:-eng+rus} +export OCR_AUTO_LANG=${OCR_AUTO_LANG:-true} export OCR_CONFIG=${OCR_CONFIG:---oem 1 --psm 6} # Point to tessdata_best if installed export TESSDATA_PREFIX=${TESSDATA_PREFIX:-}