Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Github action and use port 80 for local testing #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

app = Flask(__name__)

from app import database
from app import utils
from app import models
from app import services
from app import views
from app import error_handlers
9 changes: 9 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os
from app import app
from flask_sqlalchemy import SQLAlchemy


db_path = os.path.join(os.path.dirname(__file__), '..', 'database.sqlite')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{}'.format(db_path)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
10 changes: 10 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from app import database


class LicencePlate(database.db.Model):
plate = database.db.Column(database.db.String(8), primary_key=True)
time = database.db.Column(database.db.BigInteger, primary_key=True)

def save(self):
database.db.session.add(self)
database.db.session.commit()
78 changes: 78 additions & 0 deletions app/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from app import models
from app import utils
import cv2
import imutils
import numpy
import pytesseract
import time


def contains_licence_plates(licence_plates, date):
# noinspection PyUnresolvedReferences
stored_licence_plates = models.LicencePlate.query.filter(
models.LicencePlate.time >= date
).all()
resp = []
for licence_plate in licence_plates:
plate = next((x for x in stored_licence_plates if x.plate == licence_plate), None)
# Add date check
resp.append({
"plate": licence_plate,
"detected": plate is not None,
"time": utils.convert_timestamp_to_iso(plate.time) if plate is not None else ""
})

return resp


def parse_image(image):
models.database.db.create_all()
if image is None:
return None

image = cv2.imdecode(numpy.fromstring(image, numpy.uint8), cv2.IMREAD_UNCHANGED)
image = cv2.resize(image, (640, 480)) # reduce image size

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # remove colors
gray = cv2.bilateralFilter(gray, 13, 15, 15) # blur image to remove noise

# find image contours
edges = cv2.Canny(gray, 30, 200) # detect image edges
contours = cv2.findContours(edges.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]

plate = None
for contour in contours:
# approximate the contour
approx = cv2.approxPolyDP(contour, 0.018 * cv2.arcLength(contour, True), True)
# if our approximated contour has four points, then we can assume that it is plate
if len(approx) == 4:
plate = approx
break

if plate is None:
return None

# Masking the part other than the number plate
mask = numpy.zeros(gray.shape, numpy.uint8)
cv2.drawContours(mask, [plate], 0, 255, -1, )
cv2.bitwise_and(image, image, mask=mask)

# Now crop
(x, y) = numpy.where(mask == 255)
(top_x, top_y) = (numpy.min(x), numpy.min(y))
(bottom_x, bottom_y) = (numpy.max(x), numpy.max(y))
cropped = gray[top_x:bottom_x + 1, top_y:bottom_y + 1]

# Read the number plate
config = utils.get_tesseract_config(pytesseract)
text = pytesseract.image_to_string(cropped, config=config)

# remove new lines and remove '-' character
return text.partition("\n")[0].replace('-', '')


def save_licence_plate(plate):
licence_plate = models.LicencePlate(plate=plate, time=int(time.time()))
licence_plate.save()
23 changes: 23 additions & 0 deletions app/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from dateutil import parser
from datetime import datetime
import time
import os


def convert_iso_to_timestamp(date):
return int(time.mktime(parser.isoparse(date).timetuple()))


def convert_timestamp_to_iso(timestamp):
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%dT%H:%M:%S')


# noinspection SpellCheckingInspection
def get_tesseract_config(pytesseract):
# for Linux it's usually available trough environment path
if os.name == 'nt':
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

# we could also use user-pattern like \A\A \d\d\d-\A\A
config = '--psm 11 -c tessedit_char_whitelist=123456789ABCDEFGHIJKLMNOPRSTUVZ-'
return config
28 changes: 11 additions & 17 deletions app/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from app import app
from flask import jsonify, request
from app import services
from app import utils


def error_response(message):
Expand All @@ -21,24 +23,16 @@ def upload_camera_image():
if image.mimetype not in ["image/png", "image/jpg", "image/jpeg"]:
return error_response("Allowed image files are in PNG or JPG format")

# TODO add OCR logic here
return jsonify({"message": "Image was successfully stored"})


# TODO use database instead of hardcoded value
sk_lp = {
"RI1234AB": "2020-12-01T22:45:37"
}
plate = services.parse_image(image.read())
if plate is None:
return jsonify({"message": "No licence plate found", "status": 404})
else:
services.save_licence_plate(plate)
return jsonify({"message": f"Licence plate '{plate}' found", "status": 200})


@app.route("/check-licence-plate/<licence_plates>/<date>", methods=["GET"])
def upload_image(licence_plates, date):
resp = {}
for licence_plate in licence_plates.split(","):
# TODO Fetch data rom database for given date and licence plate
resp[licence_plate] = {
"detected": licence_plate in sk_lp,
"time": sk_lp[licence_plate] if licence_plate in sk_lp else ""
}

return jsonify(resp)
licence_plates = licence_plates.split(',')
date = utils.convert_iso_to_timestamp(date)
return jsonify(services.contains_licence_plates(licence_plates, date))
Binary file added database.sqlite
Binary file not shown.
7 changes: 6 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
Flask==1.1.2
Flask==1.1.2
python-dateutil==2.8.1
opencv-python==4.5.1.48
imutils==0.5.4
numpy==1.20.1
pytesseract==0.3.7
2 changes: 1 addition & 1 deletion run.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from app import app

if __name__ == '__main__':
app.run(debug=True)
app.run(debug=True, port=80)