Author(s): Lu Zhenna Originally published on Towards AI. Summary This article briefly walks through a solution to extract text from variable-line license plates using the pre-trained model and explains step-by-step on how to build a web app using Streamlit to deliver this solution to users. Streamlit is an open-source app framework built specifically for Machine Learning and Data Science projects. Target Audience Data scientists who are interested to learn web development but think that life is too short to learn html, css, js. Aspiring machine learning engineers who are interested to turn your python code into shareable web apps . Outline Detect the License Plate closest to the camera Use easyocr or tesseract to extract text Build an app to take an image as input and then output a string Problem Statement Given a 2D image, taken from variable angles, distances and luminance conditions, of one or multiple vehicles, we want to extract the license plate number of the vehicle closest to the camera. All license plates are from Singapore, which means the license plate number only consists of English characters and numbers with one or two lines in variable lengths. Source: Your handy guide to catching taxis in Singapore published in Executive Traveller. Detect the License Plate closest to the camera I will use a pre-trained yolov5 model without fine-tuning, because I don’t have data. Here is my code: import yolov5import torchdef inference( path2img: str, show_img: bool = False, size_img: int = 640, nms_conf_thresh: float = 0.7, max_detect: int = 10,) -> torch.Tensor: model = yolov5.load("keremberke/yolov5m-license-plate") model.conf = nms_conf_thresh model.iou = 0.45 model.agnostic = False model.multi_label = False model.max_det = max_detect results = model(path2img, size=size_img) results = model(path2img, augment=True) if show_img: results.show() return results.pred[0] I know you must wonder why I don’t just set max_detect as 1 and directly get one bounding box instead of multiple? It’s because every predicted bounding box has a confidence score. With max_detect = 1 , you get the most confident prediction which may not always be the license plate closest to the camera. Use pre-trained yolov5 model to detect all license plates. So I have to calculate the area of all predicted bounding box and keep the one with the biggest area. That will always give me the closest license plate even when the confidence is low due to skewed angle etc. Get the license plate closest to the camera. 2. Use easyocr or tesseract to extract text If your license plate has fixed length and only a single line, tesseract is mostly enough. However, from my experience, it performs poorly with number 9 and character Z and occasionally D. My code is pasted below. Please change the tessedit_char_whitelist and lang accordingly, if your license plates contain non-English characters. import pytesseractdef ocr_tesseract(path2img): text = pytesseract.image_to_string( path2img, lang="eng", config="–oem 3 –psm 6 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", ) return text It can successfully recognize one-line license number, however, tesseract fails to extract the second line for the image below. Source: Toyota Wish Taxi in Singapore 23.9.2017 2926 taken by Elmar This red taxi carplate is considerably difficult for tesseract because it contains D, 9 and Z. Let’s try easyocr instead! easyocr detection output Despite of the challenges, easyocr successfully recognized most characters except for 5! I would definitely recommend easyocr over tesseract. Here is my code: def ocr_easyocr(path2img): image = cv2.imread(path2img) reader = easyocr.Reader(["en"], gpu=False) detections = reader.readtext(image) plate_no = [] [plate_no.append(line[1]) for line in detections] return "".join(plate_no) 3. Build an app to take an image as input and then output a string Here comes the most exciting part! For users to make use of what we have built, we need a web application to upload image and read the decoded license plate string. To break it down even further: 3.1 Display title and subtitle 3.2 Display a file uploader for users to upload an image 3.3 Show recognized license plate number as text output 3.4 Display the uploaded image with predicted bounding box 3.1 Display title and subtitle First, let’s install streamlit by running pip install streamlit. Next, open the Streamlit UI by running streamlit hello in the terminal. The UI will pop up in the default browser. Have fun exploring the demo! 3.2 Display a file uploader for users to upload an image Now, let’s add a title for our web app. I created a python script in src/ and named it app.py. import streamlit as stdef app(): st.header("License Plate Recognition Web App") st.subheader("Powered by YOLOv5") st.write("Welcome!")if __name__ == "__main__": app() Then I ran the command streamlit run src/app.py in the terminal. Run `streamlit run ’ to see changes in web app. Refresh the web page you will see the title and subtitle appear there! You don’t need html syntax at all! Title appeared in the streamlit web app. Time to add more widgets! I want a file uploader with buttons for users to click. The new app function is below. def app(): st.header("License Plate Recognition Web App") st.subheader("Powered by YOLOv5") st.write("Welcome!") # add file uploader with st.form("my_uploader"): uploaded_file = st.file_uploader( "Upload image", type=["png", "jpg", "jpeg"], accept_multiple_files=False ) submit = st.form_submit_button(label="Upload") Run the streamlit run command again and you will see the file uploader appear. You don’t need any css styling. File uploader has been added. 3.3 Show recognized license plate number as text output Once users upload an image (trigger: click the upload button), we can use the relevant python functions to make an inference. The license plate number will be displayed as text. def app(): st.header("License Plate Recognition Web App") st.subheader("Powered by YOLOv5") st.write("Welcome!") with st.form("my_uploader"): uploaded_file = st.file_uploader( "Upload image", type=["png", "jpg", "jpeg"], accept_multiple_files=False ) submit = st.form_submit_button(label="Upload") if uploaded_file is not None: # save uploaded image save_path = os.path.join("temp", uploaded_file.name) with open(save_path, "wb") as f: f.write(uploaded_file.getbuffer()) if submit: # display license plate as text text = run_license_plate_recognition(save_path).recognize_text() st.write(f"Detected License Plate Number: {text}")if __name__ == "__main__": app() Display the extracted license plate number as text. 3.4 Display the uploaded image with predicted bounding box Although we have both input and output […]