Detect and Recognize Car License Plate from a video in real time (original) (raw)

`import cv2 import numpy as np from skimage.filters import threshold_local import tensorflow as tf from skimage import measure import imutils import os

def sort_cont(character_contours): """ To sort contours """ i = 0 boundingBoxes = [cv2.boundingRect(c) for c in character_contours]

(character_contours, boundingBoxes) = zip(*sorted(zip(character_contours,
                                                      boundingBoxes),
                                                  key = lambda b: b[1][i],
                                                  reverse = False))
 
return character_contours

def segment_chars(plate_img, fixed_width):

"""
extract Value channel from the HSV format
of image and apply adaptive thresholding
to reveal the characters on the license plate
"""
V = cv2.split(cv2.cvtColor(plate_img, cv2.COLOR_BGR2HSV))[2]

thresh = cv2.adaptiveThreshold(V, 255,
                               cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                               cv2.THRESH_BINARY,
                               11, 2)

thresh = cv2.bitwise_not(thresh)

# resize the license plate region to
# a canoncial size
plate_img = imutils.resize(plate_img, width = fixed_width)
thresh = imutils.resize(thresh, width = fixed_width)
bgr_thresh = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)

# perform a connected components analysis
# and initialize the mask to store the locations
# of the character candidates
labels = measure.label(thresh, background = 0)

charCandidates = np.zeros(thresh.shape, dtype ='uint8')

# loop over the unique components
characters = []
for label in np.unique(labels):
     
    # if this is the background label, ignore it
    if label == 0:
        continue
    # otherwise, construct the label mask to display
    # only connected components for the current label,
    # then find contours in the label mask
    labelMask = np.zeros(thresh.shape, dtype ='uint8')
    labelMask[labels == label] = 255

    cnts = cv2.findContours(labelMask,
                 cv2.RETR_EXTERNAL,
                 cv2.CHAIN_APPROX_SIMPLE)

    cnts = cnts[1] if imutils.is_cv3() else cnts[0]

    # ensure at least one contour was found in the mask
    if len(cnts) > 0:

        # grab the largest contour which corresponds
        # to the component in the mask, then grab the
        # bounding box for the contour
        c = max(cnts, key = cv2.contourArea)
        (boxX, boxY, boxW, boxH) = cv2.boundingRect(c)

        # compute the aspect ratio, solodity, and
        # height ration for the component
        aspectRatio = boxW / float(boxH)
        solidity = cv2.contourArea(c) / float(boxW * boxH)
        heightRatio = boxH / float(plate_img.shape[0])

        # determine if the aspect ratio, solidity,
        # and height of the contour pass the rules
        # tests
        keepAspectRatio = aspectRatio < 1.0
        keepSolidity = solidity > 0.15
        keepHeight = heightRatio > 0.5 and heightRatio < 0.95

        # check to see if the component passes
        # all the tests
        if keepAspectRatio and keepSolidity and keepHeight and boxW > 14:
             
            # compute the convex hull of the contour
            # and draw it on the character candidates
            # mask
            hull = cv2.convexHull(c)

            cv2.drawContours(charCandidates, [hull], -1, 255, -1)

contours, hier = cv2.findContours(charCandidates,
                                     cv2.RETR_EXTERNAL,
                                     cv2.CHAIN_APPROX_SIMPLE)
 
if contours:
    contours = sort_cont(contours)
     
    # value to be added to each dimension
    # of the character
    addPixel = 4 
    for c in contours:
        (x, y, w, h) = cv2.boundingRect(c)
        if y > addPixel:
            y = y - addPixel
        else:
            y = 0
        if x > addPixel:
            x = x - addPixel
        else:
            x = 0
        temp = bgr_thresh[y:y + h + (addPixel * 2),
                          x:x + w + (addPixel * 2)]

        characters.append(temp)
         
    return characters
 
else:
    return None

class PlateFinder: def init(self, minPlateArea, maxPlateArea):

    # minimum area of the plate
    self.min_area = minPlateArea
     
    # maximum area of the plate
    self.max_area = maxPlateArea 

    self.element_structure = cv2.getStructuringElement(
                          shape = cv2.MORPH_RECT, ksize =(22, 3))

def preprocess(self, input_img):
     
    imgBlurred = cv2.GaussianBlur(input_img, (7, 7), 0)
     
    # convert to gray
    gray = cv2.cvtColor(imgBlurred, cv2.COLOR_BGR2GRAY)
     
    # sobelX to get the vertical edges
    sobelx = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize = 3) 
     
    # otsu's thresholding
    ret2, threshold_img = cv2.threshold(sobelx, 0, 255,
                     cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    element = self.element_structure
    morph_n_thresholded_img = threshold_img.copy()
    cv2.morphologyEx(src = threshold_img,
                     op = cv2.MORPH_CLOSE,
                     kernel = element,
                     dst = morph_n_thresholded_img)
     
    return morph_n_thresholded_img

def extract_contours(self, after_preprocess):
     
    contours, _ = cv2.findContours(after_preprocess,
                                      mode = cv2.RETR_EXTERNAL,
                                      method = cv2.CHAIN_APPROX_NONE)
    return contours

def clean_plate(self, plate):
     
    gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY)
    thresh = cv2.adaptiveThreshold(gray,
                                   255,
                                   cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                   cv2.THRESH_BINARY,
                                   11, 2)
     
    contours, _ = cv2.findContours(thresh.copy(),
                                      cv2.RETR_EXTERNAL,
                                      cv2.CHAIN_APPROX_NONE)

    if contours:
        areas = [cv2.contourArea(c) for c in contours]
         
        # index of the largest contour in the area
        # array
        max_index = np.argmax(areas) 

        max_cnt = contours[max_index]
        max_cntArea = areas[max_index]
        x, y, w, h = cv2.boundingRect(max_cnt)
        rect = cv2.minAreaRect(max_cnt)
        if not self.ratioCheck(max_cntArea, plate.shape[1],
                                            plate.shape[0]):
            return plate, False, None
         
        return plate, True, [x, y, w, h]
     
    else:
        return plate, False, None


def check_plate(self, input_img, contour):
     
    min_rect = cv2.minAreaRect(contour)
     
    if self.validateRatio(min_rect):
        x, y, w, h = cv2.boundingRect(contour)
        after_validation_img = input_img[y:y + h, x:x + w]
        after_clean_plate_img, plateFound, coordinates = self.clean_plate(
                                                    after_validation_img)
         
        if plateFound:
            characters_on_plate = self.find_characters_on_plate(
                                          after_clean_plate_img)
             
            if (characters_on_plate is not None and len(characters_on_plate) == 8):
                x1, y1, w1, h1 = coordinates
                coordinates = x1 + x, y1 + y
                after_check_plate_img = after_clean_plate_img
                 
                return after_check_plate_img, characters_on_plate, coordinates
     
    return None, None, None


def find_possible_plates(self, input_img):
     
    """
    Finding all possible contours that can be plates
    """
    plates = []
    self.char_on_plate = []
    self.corresponding_area = []

    self.after_preprocess = self.preprocess(input_img)
    possible_plate_contours = self.extract_contours(self.after_preprocess)

    for cnts in possible_plate_contours:
        plate, characters_on_plate, coordinates = self.check_plate(input_img, cnts)
         
        if plate is not None:
            plates.append(plate)
            self.char_on_plate.append(characters_on_plate)
            self.corresponding_area.append(coordinates)

    if (len(plates) > 0):
        return plates
     
    else:
        return None

def find_characters_on_plate(self, plate):

    charactersFound = segment_chars(plate, 400)
    if charactersFound:
        return charactersFound

# PLATE FEATURES
def ratioCheck(self, area, width, height):
     
    min = self.min_area
    max = self.max_area

    ratioMin = 3
    ratioMax = 6

    ratio = float(width) / float(height)
     
    if ratio < 1:
        ratio = 1 / ratio
     
    if (area < min or area > max) or (ratio < ratioMin or ratio > ratioMax):
        return False
     
    return True

def preRatioCheck(self, area, width, height):
     
    min = self.min_area
    max = self.max_area

    ratioMin = 2.5
    ratioMax = 7

    ratio = float(width) / float(height)
     
    if ratio < 1:
        ratio = 1 / ratio

    if (area < min or area > max) or (ratio < ratioMin or ratio > ratioMax):
        return False
     
    return True

def validateRatio(self, rect):
    (x, y), (width, height), rect_angle = rect

    if (width > height):
        angle = -rect_angle
    else:
        angle = 90 + rect_angle

    if angle > 15:
        return False
     
    if (height == 0 or width == 0):
        return False

    area = width * height
     
    if not self.preRatioCheck(area, width, height):
        return False
    else:
        return True

`