OpenCV’s dnn module ships two high-level wrappers, TextDetectionModel_DB and TextDetectionModel_EAST, that load a pretrained network, run it, and do the post-processing (thresholding, unclipping, non-maximum suppression) inside OpenCV. You get boxes back with no PyTorch or TensorFlow installed. Everything below was checked against OpenCV 4.x’s text-spotting tutorial; the model files come from the links on that page.
Setup
pip install "opencv-python>=4.8" numpy
Model files:
- DB:
DB_IC15_resnet18.onnx(trained on ICDAR 2015, best for scene text) orDB_TD500_resnet50.onnx(trained on MSRA-TD500, better for long lines). Both are linked from the OpenCV text spotting tutorial. - EAST:
frozen_east_text_detection.pb, the frozen TensorFlow graph from the same tutorial.
DBNet: polygons for any shape
import cv2
import numpy as np
detector = cv2.dnn.TextDetectionModel_DB("DB_IC15_resnet18.onnx")
detector.setBinaryThreshold(0.3)
detector.setPolygonThreshold(0.5)
detector.setMaxCandidates(200)
detector.setUnclipRatio(2.0)
# Normalisation values the model was trained with; keep them exactly.
detector.setInputParams(
scale=1.0 / 255.0,
size=(736, 736),
mean=(122.67891434, 116.66876762, 104.00698793),
swapRB=False,
)
image = cv2.imread("photo.jpg")
polygons, confidences = detector.detect(image)
for quad, score in zip(polygons, confidences):
pts = np.array(quad, dtype=np.int32).reshape(-1, 1, 2)
cv2.polylines(image, [pts], isClosed=True, color=(74, 242, 144), thickness=2)
x, y = pts[0][0]
cv2.putText(image, f"{score:.2f}", (int(x), int(y) - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (74, 242, 144), 1)
cv2.imwrite("photo-db.jpg", image)
print(f"{len(polygons)} text regions")
What the knobs mean (all four are the DBNet post-processing parameters):
| Setter | Effect |
|---|---|
setBinaryThreshold |
probability above which a pixel is text |
setPolygonThreshold |
minimum mean score inside a region to keep it |
setUnclipRatio |
how far the shrunken kernel is expanded back to the full text; raise it if boxes are tight, lower it if neighbours merge |
setMaxCandidates |
cap on regions considered |
The input size does not have to be 736 × 736, but both sides must be multiples of 32. Larger sizes find smaller text and cost time quadratically.
EAST: rotated boxes, very fast
import cv2
import numpy as np
detector = cv2.dnn.TextDetectionModel_EAST("frozen_east_text_detection.pb")
detector.setConfidenceThreshold(0.5)
detector.setNMSThreshold(0.4)
detector.setInputParams(
scale=1.0,
size=(320, 320), # multiples of 32
mean=(123.68, 116.78, 103.94),
swapRB=True,
)
image = cv2.imread("photo.jpg")
boxes, confidences = detector.detect(image) # each box is 4 points of a rotated rectangle
for quad in boxes:
pts = np.array(quad, dtype=np.int32).reshape(-1, 1, 2)
cv2.polylines(image, [pts], isClosed=True, color=(74, 242, 144), thickness=2)
cv2.imwrite("photo-east.jpg", image)
EAST outputs rotated rectangles, so it copes with tilted signs but cannot follow curves. A 320 × 320 input is what the original tutorial used; 640 × 640 finds smaller words at four times the cost.
Which one?
DB (DB_IC15_resnet18) |
EAST | |
|---|---|---|
| Output | polygons (returned as 4-point quads by OpenCV) | rotated rectangles |
| Curved text | yes | no |
| Long lines | fair (use the TD500 model) | splits them |
| Speed on CPU | moderate | fast |
| Age | 2020 | 2017 |
For anything new, start with DB. Keep EAST for embedded CPUs where its speed matters and the text is straight.
Reading the text afterwards
OpenCV also has cv2.dnn.TextRecognitionModel, which runs a CRNN recognizer on each crop; the tutorial page shows the vocabulary file and preprocessing. In practice, once you need recognition it is simpler to move to a toolkit that bundles both steps: see text detection in Python, or use the detector on this site, which runs a DB-family model and a recognizer in the browser.
Common problems
- Nothing detected. Check
swapRBandmean; the DB models expect BGR input with the mean above, EAST expects RGB. - Boxes offset from the text.
detect()returns coordinates in the original image space only when you pass the original image; do not resize before calling it. - Slow. Reduce the input size, or build OpenCV with CUDA and call
detector.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA).