Skip to content

Instantly share code, notes, and snippets.

@synap5e
Last active November 20, 2019 02:41
Show Gist options
  • Save synap5e/fb32ffff0e0db8de15064cf1b8bb6dfa to your computer and use it in GitHub Desktop.
Save synap5e/fb32ffff0e0db8de15064cf1b8bb6dfa to your computer and use it in GitHub Desktop.
OpenCV OCR segmentation
import cv2
import matplotlib.pyplot as plt
import numpy as np
def main() -> None:
f, (ax1, ax2) = plt.subplots(2, 1)
im = cv2.imread('number_plate.png')
# experiment with what works best here...
# If the background can have bright colours but never white then np.min(im, axis=2) works well
# If the text is a specific colour you could use HSV and do an inrange where the hue is correct and S/V match the text being foreground
# If it's just a black/white then bgr2gray is simplest
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
# Find some threshold that works
# OTSU is tempting, but can make the stroke vary depending on the background so if you can get a clean thresh with a value use that
# invert as necessary
# Add an empty border so our segmenting doesn't get unmatched start/ends
_, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY | cv2.THRESH_BINARY_INV)
thresh = cv2.copyMakeBorder(thresh, 1, 1, 1, 1, borderType=cv2.BORDER_CONSTANT, value=0)
ax1.imshow(thresh)
cols = cv2.reduce(thresh, 0, cv2.REDUCE_AVG)[0]
ax1.plot(cols, color='r')
# The peak threshold (height) is set somewhere that it will still detect the horizontal stroke of e.g. L, H, etc. as the same
# letter but can filter noise. If the image is super noisy play with the morphology and/or blurring etc. of gray/thresh images
gaps = cols < 10
# edges now contains the start and end of each segment, with -1=segment start and +1=segment end
edges = gaps[1:].astype(np.int) - gaps[:-1]
ax1.plot(100 + edges * 50, color='g') # transform signal to show on graph properly
segment_starts = np.where(edges == -1)[0]
segment_ends = np.where(edges == 1)[0]
ax1.scatter(segment_starts, [40 for _ in segment_starts], color='blue')
ax1.scatter(segment_ends, [40 for _ in segment_ends], color='orange')
assert len(segment_starts) == len(segment_ends), 'Got unmatched segment start/ends. Do any characters/segmentations touch the edges of the image?'
segments = []
for start, end in zip(segment_starts, segment_ends):
segments.append(im[:, max(0, start - 1):min(end + 1, im.shape[1] - 1)])
ax2.imshow(np.hstack([
cv2.copyMakeBorder(s, 0, 0, 0, 50 - s.shape[1], cv2.BORDER_CONSTANT, value=(255, 0, 255))
for s in segments
]))
plt.show()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment