File size: 5,326 Bytes
5e41317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# SPDX-License-Identifier: MIT
# Copyright (C) Intel Corporation
#
# OCR for Text: DLStreamer pipeline sample.
# See README.md for prerequisites and inputs. Run: python3 dlstreamer_sample.py

import gi

gi.require_version("Gst", "1.0")
from gi.repository import Gst

import numpy as np
import openvino as ov

Gst.init([])

# Import cv2 after Gst.init to avoid GStreamer re-initialization conflicts.
import cv2

INPUT_VIDEO = "test_video.mp4"
DET_MODEL = "ch_PP-OCRv4_det_infer/inference.xml"
REC_MODEL = "ch_PP-OCRv4_rec_server_infer/inference.xml"
DICT_FILE = "ppocr_keys_v1.txt"
DET_SIZE = 960

core = ov.Core()
det_compiled = core.compile_model(core.read_model(DET_MODEL), "CPU")
rec_compiled = core.compile_model(core.read_model(REC_MODEL), "CPU")

# CTC label map: index 0 is the blank symbol, followed by every character in
# the dictionary file, followed by a trailing space character.
chars = open(DICT_FILE, encoding="utf-8").read().splitlines()
dict_character = ["blank"] + chars + [" "]


def detect_text_regions(frame, thresh=0.3, pad=4):
    """Return (x, y, w, h) boxes for words/lines of text in a frame.

    Resizing preserves aspect ratio (letterboxed onto a square canvas) so
    text isn't skewed, and dilating the detection map merges nearby
    characters into one box per word instead of one per character.
    """
    h0, w0 = frame.shape[:2]
    scale = DET_SIZE / max(h0, w0)
    resized = cv2.resize(frame, (int(w0 * scale), int(h0 * scale)))
    canvas = np.zeros((DET_SIZE, DET_SIZE, 3), dtype=np.uint8)
    canvas[:resized.shape[0], :resized.shape[1]] = resized

    blob = canvas.astype(np.float32).transpose(2, 0, 1)[np.newaxis] / 255.0
    det_map = det_compiled([blob])[det_compiled.output(0)][0, 0]
    binary = (det_map > thresh).astype(np.uint8) * 255
    dilated = cv2.dilate(binary, np.ones((9, 25), np.uint8))
    contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    boxes = []
    for c in contours:
        x, y, w, h = cv2.boundingRect(c)
        if w < 10 or h < 5:
            continue
        x0, y0 = max(0, x / scale - pad), max(0, y / scale - pad)
        x1, y1 = min(w0, (x + w) / scale + pad), min(h0, (y + h) / scale + pad)
        boxes.append((int(x0), int(y0), int(x1 - x0), int(y1 - y0)))
    return boxes


def recognize_text(crop, rec_h=48, max_w=320):
    """Resize a cropped text region to the recognizer's input shape and
    CTC-decode the predicted character sequence into a string."""
    h, w = crop.shape[:2]
    if h == 0 or w == 0:
        return "", 0.0
    resized_w = max(1, min(max_w, round(rec_h * w / h)))
    blob = cv2.resize(crop, (resized_w, rec_h)).astype(np.float32) / 255.0
    blob = ((blob - 0.5) / 0.5).transpose(2, 0, 1)[np.newaxis, ...]

    preds = rec_compiled([blob])[rec_compiled.output(0)][0]
    idx = np.argmax(preds, axis=1)
    conf = np.max(preds, axis=1)

    text, scores, prev = [], [], -1
    for i, c in zip(idx, conf):
        if i != 0 and i != prev:
            text.append(dict_character[i])
            scores.append(c)
        prev = i
    confidence = float(np.mean(scores)) if scores else 0.0
    return "".join(text), confidence


def annotate(frame, box, text, confidence):
    """Draw a bounding box and, if any text was recognized, a legible
    label (solid background so it stays readable over any color) above it."""
    x, y, w, h = box
    cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    if not text:
        return
    label = f"{text} ({confidence:.2f})"
    (tw, th), base = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
    top = max(0, y - th - base - 6)
    cv2.rectangle(frame, (x, top), (x + tw + 6, top + th + base + 6), (0, 255, 0), -1)
    cv2.putText(frame, label, (x + 3, top + th + 2),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2)


pipeline_str = (
    f"filesrc location={INPUT_VIDEO} ! decodebin3 ! videoconvert ! "
    "video/x-raw,format=BGR ! "
    "appsink name=sink emit-signals=false sync=false"
)
pipeline = Gst.parse_launch(pipeline_str)
sink = pipeline.get_by_name("sink")
pipeline.set_state(Gst.State.PLAYING)

writer = None
frame_idx = 0
total_regions = 0

while True:
    sample = sink.emit("pull-sample")
    if sample is None:
        break
    buf = sample.get_buffer()
    caps = sample.get_caps().get_structure(0)
    width = caps.get_value("width")
    height = caps.get_value("height")

    ok, mapinfo = buf.map(Gst.MapFlags.READ)
    if not ok:
        continue
    frame = np.ndarray((height, width, 3), dtype=np.uint8,
                       buffer=mapinfo.data).copy()
    buf.unmap(mapinfo)
    frame_idx += 1

    for box in detect_text_regions(frame):
        x, y, w, h = box
        text, confidence = recognize_text(frame[y:y + h, x:x + w])
        annotate(frame, box, text, confidence)
        total_regions += 1
        print(f"Frame {frame_idx}: region=({x},{y},{w},{h}) text={text!r} "
              f"confidence={confidence:.2f}", flush=True)

    if writer is None:
        writer = cv2.VideoWriter(
            "output_dlstreamer.mp4", cv2.VideoWriter_fourcc(*"mp4v"),
            30.0, (width, height))
    writer.write(frame)

pipeline.set_state(Gst.State.NULL)
if writer:
    writer.release()
print(f"Total text regions across all frames: {total_regions}", flush=True)