# Import required modules
import cv2 as cv
import math
import argparse
parser = argparse.ArgumentParser(description='Use this script to run text detection deep learning networks using OpenCV.')
# Input argument
parser.add_argument('--input', help='Path to input image or video file. Skip this argument to capture frames from a camera.')
# Model argument
parser.add_argument('--model', default="frozen_east_text_detection.pb",
help='Path to a binary .pb file of model contains trained weights.'
)
# Width argument
parser.add_argument('--width', type=int, default=320,
help='Preprocess input image by resizing to a specific width. It should be multiple by 32.'
)
# Height argument
parser.add_argument('--height',type=int, default=320,
help='Preprocess input image by resizing to a specific height. It should be multiple by 32.'
)
# Confidence threshold
parser.add_argument('--thr',type=float, default=0.5,
help='Confidence threshold.'
)
# Non-maximum suppression threshold
parser.add_argument('--nms',type=float, default=0.4,
help='Non-maximum suppression threshold.'
)
args = parser.parse_args()
############ Utility functions ############
def decode(scores, geometry, scoreThresh):
detections = []
confidences = []
############ CHECK DIMENSIONS AND SHAPES OF geometry AND scores ############
assert len(scores.shape) == 4, "Incorrect dimensions of scores"
assert len(geometry.shape) == 4, "Incorrect dimensions of geometry"
assert scores.shape[0] == 1, "Invalid dimensions of scores"
assert geometry.shape[0] == 1, "Invalid dimensions of geometry"
assert scores.shape[1] == 1, "Invalid dimensions of scores"
assert geometry.shape[1] == 5, "Invalid dimensions of geometry"
assert scores.shape[2] == geometry.shape[2], "Invalid dimensions of scores and geometry"
assert scores.shape[3] == geometry.shape[3], "Invalid dimensions of scores and geometry"
height = scores.shape[2]
width = scores.shape[3]
for y in range(0, height):
# Extract data from scores
scoresData = scores[0][0][y]
x0_data = geometry[0][0][y]
x1_data = geometry[0][1][y]
x2_data = geometry[0][2][y]
x3_data = geometry[0][3][y]
anglesData = geometry[0][4][y]
for x in range(0, width):
score = scoresData[x]
# If score is lower than threshold score, move to next x
if(score < scoreThresh):
continue
# Calculate offset
offsetX = x * 4.0
offsetY = y * 4.0
angle = anglesData[x]
# Calculate cos and sin of angle
cosA = math.cos(angle)
sinA = math.sin(angle)
h = x0_data[x] + x2_data[x]
w = x1_data[x] + x3_data[x]
# Calculate offset
offset = ([offsetX + cosA * x1_data[x] + sinA * x2_data[x], offsetY - sinA * x1_data[x] + cosA * x2_data[x]])
# Find points for rectangle
p1 = (-sinA * h + offset[0], -cosA * h + offset[1])
p3 = (-cosA * w + offset[0], sinA * w + offset[1])
center = (0.5*(p1[0]+p3[0]), 0.5*(p1[1]+p3[1]))
detections.append((center, (w,h), -1*angle * 180.0 / math.pi))
confidences.append(float(score))
# Return detections and confidences
return [detections, confidences]
if __name__ == "__main__":
# Read and store arguments
confThreshold = args.thr
nmsThreshold = args.nms
inpWidth = args.width
inpHeight = args.height
model = args.model
# Load network
net = cv.dnn.readNet(model)
# Create a new named window
kWinName = "EAST: An Efficient and Accurate Scene Text Detector"
cv.namedWindow(kWinName, cv.WINDOW_NORMAL)
outputLayers = []
outputLayers.append("feature_fusion/Conv_7/Sigmoid")
outputLayers.append("feature_fusion/concat_3")
# Open a video file or an image file or a camera stream
cap = cv.VideoCapture(args.input if args.input else 0)
while cv.waitKey(1) < 0:
# Read frame
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
# Get frame height and width
height_ = frame.shape[0]
width_ = frame.shape[1]
rW = width_ / float(inpWidth)
rH = height_ / float(inpHeight)
# Create a 4D blob from frame.
blob = cv.dnn.blobFromImage(frame, 1.0, (inpWidth, inpHeight), (123.68, 116.78, 103.94), True, False)
# Run the model
net.setInput(blob)
output = net.forward(outputLayers)
t, _ = net.getPerfProfile()
label = 'Inference time: %.2f ms' % (t * 1000.0 / cv.getTickFrequency())
# Get scores and geometry
scores = output[0]
geometry = output[1]
[boxes, confidences] = decode(scores, geometry, confThreshold)
# Apply NMS
indices = cv.dnn.NMSBoxesRotated(boxes, confidences, confThreshold,nmsThreshold)
for i in indices:
# get 4 corners of the rotated rect
vertices = cv.boxPoints(boxes[i[0]])
# scale the bounding box coordinates based on the respective ratios
for j in range(4):
vertices[j][0] *= rW
vertices[j][1] *= rH
for j in range(4):
p1 = (vertices[j][0], vertices[j][1])
p2 = (vertices[(j + 1) % 4][0], vertices[(j + 1) % 4][1])
cv.line(frame, p1, p2, (0, 255, 0), 2, cv.LINE_AA);
# cv.putText(frame, "{:.3f}".format(confidences[i[0]]), (vertices[0][0], vertices[0][1]), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1, cv.LINE_AA)
# Put efficiency information
cv.putText(frame, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))
# Display the frame
cv.imshow(kWinName,frame)
cv.imwrite("out-{}".format(args.input),frame)
IyBJbXBvcnQgcmVxdWlyZWQgbW9kdWxlcwppbXBvcnQgY3YyIGFzIGN2CmltcG9ydCBtYXRoCmltcG9ydCBhcmdwYXJzZQoKcGFyc2VyID0gYXJncGFyc2UuQXJndW1lbnRQYXJzZXIoZGVzY3JpcHRpb249J1VzZSB0aGlzIHNjcmlwdCB0byBydW4gdGV4dCBkZXRlY3Rpb24gZGVlcCBsZWFybmluZyBuZXR3b3JrcyB1c2luZyBPcGVuQ1YuJykKIyBJbnB1dCBhcmd1bWVudApwYXJzZXIuYWRkX2FyZ3VtZW50KCctLWlucHV0JywgaGVscD0nUGF0aCB0byBpbnB1dCBpbWFnZSBvciB2aWRlbyBmaWxlLiBTa2lwIHRoaXMgYXJndW1lbnQgdG8gY2FwdHVyZSBmcmFtZXMgZnJvbSBhIGNhbWVyYS4nKQojIE1vZGVsIGFyZ3VtZW50CnBhcnNlci5hZGRfYXJndW1lbnQoJy0tbW9kZWwnLCBkZWZhdWx0PSJmcm96ZW5fZWFzdF90ZXh0X2RldGVjdGlvbi5wYiIsCiAgICAgICAgICAgICAgICAgICAgaGVscD0nUGF0aCB0byBhIGJpbmFyeSAucGIgZmlsZSBvZiBtb2RlbCBjb250YWlucyB0cmFpbmVkIHdlaWdodHMuJwogICAgICAgICAgICAgICAgICAgICkKIyBXaWR0aCBhcmd1bWVudApwYXJzZXIuYWRkX2FyZ3VtZW50KCctLXdpZHRoJywgdHlwZT1pbnQsIGRlZmF1bHQ9MzIwLAogICAgICAgICAgICAgICAgICAgIGhlbHA9J1ByZXByb2Nlc3MgaW5wdXQgaW1hZ2UgYnkgcmVzaXppbmcgdG8gYSBzcGVjaWZpYyB3aWR0aC4gSXQgc2hvdWxkIGJlIG11bHRpcGxlIGJ5IDMyLicKICAgICAgICAgICAgICAgICAgICkKIyBIZWlnaHQgYXJndW1lbnQKcGFyc2VyLmFkZF9hcmd1bWVudCgnLS1oZWlnaHQnLHR5cGU9aW50LCBkZWZhdWx0PTMyMCwKICAgICAgICAgICAgICAgICAgICBoZWxwPSdQcmVwcm9jZXNzIGlucHV0IGltYWdlIGJ5IHJlc2l6aW5nIHRvIGEgc3BlY2lmaWMgaGVpZ2h0LiBJdCBzaG91bGQgYmUgbXVsdGlwbGUgYnkgMzIuJwogICAgICAgICAgICAgICAgICAgKQojIENvbmZpZGVuY2UgdGhyZXNob2xkCnBhcnNlci5hZGRfYXJndW1lbnQoJy0tdGhyJyx0eXBlPWZsb2F0LCBkZWZhdWx0PTAuNSwKICAgICAgICAgICAgICAgICAgICBoZWxwPSdDb25maWRlbmNlIHRocmVzaG9sZC4nCiAgICAgICAgICAgICAgICAgICApCiMgTm9uLW1heGltdW0gc3VwcHJlc3Npb24gdGhyZXNob2xkCnBhcnNlci5hZGRfYXJndW1lbnQoJy0tbm1zJyx0eXBlPWZsb2F0LCBkZWZhdWx0PTAuNCwKICAgICAgICAgICAgICAgICAgICBoZWxwPSdOb24tbWF4aW11bSBzdXBwcmVzc2lvbiB0aHJlc2hvbGQuJwogICAgICAgICAgICAgICAgICAgKQoKYXJncyA9IHBhcnNlci5wYXJzZV9hcmdzKCkKCgojIyMjIyMjIyMjIyMgVXRpbGl0eSBmdW5jdGlvbnMgIyMjIyMjIyMjIyMjCmRlZiBkZWNvZGUoc2NvcmVzLCBnZW9tZXRyeSwgc2NvcmVUaHJlc2gpOgogICAgZGV0ZWN0aW9ucyA9IFtdCiAgICBjb25maWRlbmNlcyA9IFtdCgogICAgIyMjIyMjIyMjIyMjIENIRUNLIERJTUVOU0lPTlMgQU5EIFNIQVBFUyBPRiBnZW9tZXRyeSBBTkQgc2NvcmVzICMjIyMjIyMjIyMjIwogICAgYXNzZXJ0IGxlbihzY29yZXMuc2hhcGUpID09IDQsICJJbmNvcnJlY3QgZGltZW5zaW9ucyBvZiBzY29yZXMiCiAgICBhc3NlcnQgbGVuKGdlb21ldHJ5LnNoYXBlKSA9PSA0LCAiSW5jb3JyZWN0IGRpbWVuc2lvbnMgb2YgZ2VvbWV0cnkiCiAgICBhc3NlcnQgc2NvcmVzLnNoYXBlWzBdID09IDEsICJJbnZhbGlkIGRpbWVuc2lvbnMgb2Ygc2NvcmVzIgogICAgYXNzZXJ0IGdlb21ldHJ5LnNoYXBlWzBdID09IDEsICJJbnZhbGlkIGRpbWVuc2lvbnMgb2YgZ2VvbWV0cnkiCiAgICBhc3NlcnQgc2NvcmVzLnNoYXBlWzFdID09IDEsICJJbnZhbGlkIGRpbWVuc2lvbnMgb2Ygc2NvcmVzIgogICAgYXNzZXJ0IGdlb21ldHJ5LnNoYXBlWzFdID09IDUsICJJbnZhbGlkIGRpbWVuc2lvbnMgb2YgZ2VvbWV0cnkiCiAgICBhc3NlcnQgc2NvcmVzLnNoYXBlWzJdID09IGdlb21ldHJ5LnNoYXBlWzJdLCAiSW52YWxpZCBkaW1lbnNpb25zIG9mIHNjb3JlcyBhbmQgZ2VvbWV0cnkiCiAgICBhc3NlcnQgc2NvcmVzLnNoYXBlWzNdID09IGdlb21ldHJ5LnNoYXBlWzNdLCAiSW52YWxpZCBkaW1lbnNpb25zIG9mIHNjb3JlcyBhbmQgZ2VvbWV0cnkiCiAgICBoZWlnaHQgPSBzY29yZXMuc2hhcGVbMl0KICAgIHdpZHRoID0gc2NvcmVzLnNoYXBlWzNdCiAgICBmb3IgeSBpbiByYW5nZSgwLCBoZWlnaHQpOgoKICAgICAgICAjIEV4dHJhY3QgZGF0YSBmcm9tIHNjb3JlcwogICAgICAgIHNjb3Jlc0RhdGEgPSBzY29yZXNbMF1bMF1beV0KICAgICAgICB4MF9kYXRhID0gZ2VvbWV0cnlbMF1bMF1beV0KICAgICAgICB4MV9kYXRhID0gZ2VvbWV0cnlbMF1bMV1beV0KICAgICAgICB4Ml9kYXRhID0gZ2VvbWV0cnlbMF1bMl1beV0KICAgICAgICB4M19kYXRhID0gZ2VvbWV0cnlbMF1bM11beV0KICAgICAgICBhbmdsZXNEYXRhID0gZ2VvbWV0cnlbMF1bNF1beV0KICAgICAgICBmb3IgeCBpbiByYW5nZSgwLCB3aWR0aCk6CiAgICAgICAgICAgIHNjb3JlID0gc2NvcmVzRGF0YVt4XQoKICAgICAgICAgICAgIyBJZiBzY29yZSBpcyBsb3dlciB0aGFuIHRocmVzaG9sZCBzY29yZSwgbW92ZSB0byBuZXh0IHgKICAgICAgICAgICAgaWYoc2NvcmUgPCBzY29yZVRocmVzaCk6CiAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgIyBDYWxjdWxhdGUgb2Zmc2V0CiAgICAgICAgICAgIG9mZnNldFggPSB4ICogNC4wCiAgICAgICAgICAgIG9mZnNldFkgPSB5ICogNC4wCiAgICAgICAgICAgIGFuZ2xlID0gYW5nbGVzRGF0YVt4XQoKICAgICAgICAgICAgIyBDYWxjdWxhdGUgY29zIGFuZCBzaW4gb2YgYW5nbGUKICAgICAgICAgICAgY29zQSA9IG1hdGguY29zKGFuZ2xlKQogICAgICAgICAgICBzaW5BID0gbWF0aC5zaW4oYW5nbGUpCiAgICAgICAgICAgIGggPSB4MF9kYXRhW3hdICsgeDJfZGF0YVt4XQogICAgICAgICAgICB3ID0geDFfZGF0YVt4XSArIHgzX2RhdGFbeF0KCiAgICAgICAgICAgICMgQ2FsY3VsYXRlIG9mZnNldAogICAgICAgICAgICBvZmZzZXQgPSAoW29mZnNldFggKyBjb3NBICogeDFfZGF0YVt4XSArIHNpbkEgKiB4Ml9kYXRhW3hdLCBvZmZzZXRZIC0gc2luQSAqIHgxX2RhdGFbeF0gKyBjb3NBICogeDJfZGF0YVt4XV0pCgogICAgICAgICAgICAjIEZpbmQgcG9pbnRzIGZvciByZWN0YW5nbGUKICAgICAgICAgICAgcDEgPSAoLXNpbkEgKiBoICsgb2Zmc2V0WzBdLCAtY29zQSAqIGggKyBvZmZzZXRbMV0pCiAgICAgICAgICAgIHAzID0gKC1jb3NBICogdyArIG9mZnNldFswXSwgIHNpbkEgKiB3ICsgb2Zmc2V0WzFdKQogICAgICAgICAgICBjZW50ZXIgPSAoMC41KihwMVswXStwM1swXSksIDAuNSoocDFbMV0rcDNbMV0pKQogICAgICAgICAgICBkZXRlY3Rpb25zLmFwcGVuZCgoY2VudGVyLCAodyxoKSwgLTEqYW5nbGUgKiAxODAuMCAvIG1hdGgucGkpKQogICAgICAgICAgICBjb25maWRlbmNlcy5hcHBlbmQoZmxvYXQoc2NvcmUpKQoKICAgICMgUmV0dXJuIGRldGVjdGlvbnMgYW5kIGNvbmZpZGVuY2VzCiAgICByZXR1cm4gW2RldGVjdGlvbnMsIGNvbmZpZGVuY2VzXQoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgICMgUmVhZCBhbmQgc3RvcmUgYXJndW1lbnRzCiAgICBjb25mVGhyZXNob2xkID0gYXJncy50aHIKICAgIG5tc1RocmVzaG9sZCA9IGFyZ3Mubm1zCiAgICBpbnBXaWR0aCA9IGFyZ3Mud2lkdGgKICAgIGlucEhlaWdodCA9IGFyZ3MuaGVpZ2h0CiAgICBtb2RlbCA9IGFyZ3MubW9kZWwKCiAgICAjIExvYWQgbmV0d29yawogICAgbmV0ID0gY3YuZG5uLnJlYWROZXQobW9kZWwpCgogICAgIyBDcmVhdGUgYSBuZXcgbmFtZWQgd2luZG93CiAgICBrV2luTmFtZSA9ICJFQVNUOiBBbiBFZmZpY2llbnQgYW5kIEFjY3VyYXRlIFNjZW5lIFRleHQgRGV0ZWN0b3IiCiAgICBjdi5uYW1lZFdpbmRvdyhrV2luTmFtZSwgY3YuV0lORE9XX05PUk1BTCkKICAgIG91dHB1dExheWVycyA9IFtdCiAgICBvdXRwdXRMYXllcnMuYXBwZW5kKCJmZWF0dXJlX2Z1c2lvbi9Db252XzcvU2lnbW9pZCIpCiAgICBvdXRwdXRMYXllcnMuYXBwZW5kKCJmZWF0dXJlX2Z1c2lvbi9jb25jYXRfMyIpCgogICAgIyBPcGVuIGEgdmlkZW8gZmlsZSBvciBhbiBpbWFnZSBmaWxlIG9yIGEgY2FtZXJhIHN0cmVhbQogICAgY2FwID0gY3YuVmlkZW9DYXB0dXJlKGFyZ3MuaW5wdXQgaWYgYXJncy5pbnB1dCBlbHNlIDApCgogICAgd2hpbGUgY3Yud2FpdEtleSgxKSA8IDA6CiAgICAgICAgIyBSZWFkIGZyYW1lCiAgICAgICAgaGFzRnJhbWUsIGZyYW1lID0gY2FwLnJlYWQoKQogICAgICAgIGlmIG5vdCBoYXNGcmFtZToKICAgICAgICAgICAgY3Yud2FpdEtleSgpCiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgR2V0IGZyYW1lIGhlaWdodCBhbmQgd2lkdGgKICAgICAgICBoZWlnaHRfID0gZnJhbWUuc2hhcGVbMF0KICAgICAgICB3aWR0aF8gPSBmcmFtZS5zaGFwZVsxXQogICAgICAgIHJXID0gd2lkdGhfIC8gZmxvYXQoaW5wV2lkdGgpCiAgICAgICAgckggPSBoZWlnaHRfIC8gZmxvYXQoaW5wSGVpZ2h0KQoKICAgICAgICAjIENyZWF0ZSBhIDREIGJsb2IgZnJvbSBmcmFtZS4KICAgICAgICBibG9iID0gY3YuZG5uLmJsb2JGcm9tSW1hZ2UoZnJhbWUsIDEuMCwgKGlucFdpZHRoLCBpbnBIZWlnaHQpLCAoMTIzLjY4LCAxMTYuNzgsIDEwMy45NCksIFRydWUsIEZhbHNlKQoKICAgICAgICAjIFJ1biB0aGUgbW9kZWwKICAgICAgICBuZXQuc2V0SW5wdXQoYmxvYikKICAgICAgICBvdXRwdXQgPSBuZXQuZm9yd2FyZChvdXRwdXRMYXllcnMpCiAgICAgICAgdCwgXyA9IG5ldC5nZXRQZXJmUHJvZmlsZSgpCiAgICAgICAgbGFiZWwgPSAnSW5mZXJlbmNlIHRpbWU6ICUuMmYgbXMnICUgKHQgKiAxMDAwLjAgLyBjdi5nZXRUaWNrRnJlcXVlbmN5KCkpCgogICAgICAgICMgR2V0IHNjb3JlcyBhbmQgZ2VvbWV0cnkKICAgICAgICBzY29yZXMgPSBvdXRwdXRbMF0KICAgICAgICBnZW9tZXRyeSA9IG91dHB1dFsxXQogICAgICAgIFtib3hlcywgY29uZmlkZW5jZXNdID0gZGVjb2RlKHNjb3JlcywgZ2VvbWV0cnksIGNvbmZUaHJlc2hvbGQpCiAgICAgICAgIyBBcHBseSBOTVMKICAgICAgICBpbmRpY2VzID0gY3YuZG5uLk5NU0JveGVzUm90YXRlZChib3hlcywgY29uZmlkZW5jZXMsIGNvbmZUaHJlc2hvbGQsbm1zVGhyZXNob2xkKQogICAgICAgIGZvciBpIGluIGluZGljZXM6CiAgICAgICAgICAgICMgZ2V0IDQgY29ybmVycyBvZiB0aGUgcm90YXRlZCByZWN0CiAgICAgICAgICAgIHZlcnRpY2VzID0gY3YuYm94UG9pbnRzKGJveGVzW2lbMF1dKQogICAgICAgICAgICAjIHNjYWxlIHRoZSBib3VuZGluZyBib3ggY29vcmRpbmF0ZXMgYmFzZWQgb24gdGhlIHJlc3BlY3RpdmUgcmF0aW9zCiAgICAgICAgICAgIGZvciBqIGluIHJhbmdlKDQpOgogICAgICAgICAgICAgICAgdmVydGljZXNbal1bMF0gKj0gclcKICAgICAgICAgICAgICAgIHZlcnRpY2VzW2pdWzFdICo9IHJICiAgICAgICAgICAgIGZvciBqIGluIHJhbmdlKDQpOgogICAgICAgICAgICAgICAgcDEgPSAodmVydGljZXNbal1bMF0sIHZlcnRpY2VzW2pdWzFdKQogICAgICAgICAgICAgICAgcDIgPSAodmVydGljZXNbKGogKyAxKSAlIDRdWzBdLCB2ZXJ0aWNlc1soaiArIDEpICUgNF1bMV0pCiAgICAgICAgICAgICAgICBjdi5saW5lKGZyYW1lLCBwMSwgcDIsICgwLCAyNTUsIDApLCAyLCBjdi5MSU5FX0FBKTsKICAgICAgICAgICAgICAgICMgY3YucHV0VGV4dChmcmFtZSwgIns6LjNmfSIuZm9ybWF0KGNvbmZpZGVuY2VzW2lbMF1dKSwgKHZlcnRpY2VzWzBdWzBdLCB2ZXJ0aWNlc1swXVsxXSksIGN2LkZPTlRfSEVSU0hFWV9TSU1QTEVYLCAwLjUsICgyNTUsIDAsIDApLCAxLCBjdi5MSU5FX0FBKQoKICAgICAgICAjIFB1dCBlZmZpY2llbmN5IGluZm9ybWF0aW9uCiAgICAgICAgY3YucHV0VGV4dChmcmFtZSwgbGFiZWwsICgwLCAxNSksIGN2LkZPTlRfSEVSU0hFWV9TSU1QTEVYLCAwLjUsICgwLCAwLCAyNTUpKQoKICAgICAgICAjIERpc3BsYXkgdGhlIGZyYW1lCiAgICAgICAgY3YuaW1zaG93KGtXaW5OYW1lLGZyYW1lKQogICAgICAgIGN2Lmltd3JpdGUoIm91dC17fSIuZm9ybWF0KGFyZ3MuaW5wdXQpLGZyYW1lKQ==