├── .gitignore ├── Procfile ├── README.md ├── app.py ├── core ├── application.py └── router.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | core/__pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KTP Validation Image 2 | 3 | ## Installation 4 | 5 | * Clone the Repo 6 | * Create the environment first 7 | ```bash 8 | python -m venv env 9 | ``` 10 | * Activate the environment 11 | ```bash 12 | env\Scripts\activate.bat 13 | ``` 14 | * Install all library needed to environment by using command 15 | ```bash 16 | pip install -r requirements.txt 17 | ``` 18 | 19 | 20 | ## Usage 21 | 22 | * (For Windows) set the flask app 23 | ```bash 24 | set FLASK_APP=app.py 25 | ``` 26 | * Start server with command: 27 | ```bash 28 | python -m flask run 29 | ``` 30 | 31 | Then open [http://127.0.0.1:5000/](http://127.0.0.1:5000/) 32 | 33 | ## API 34 | 35 | | Url | Params | Type | 36 | | ------------- |:-------------:| :-----:| 37 | | /api/valid | ktp | Files | 38 | | /api/extract_ktp | ktp | Files | 39 | | /api/match | ktp,nik | Files, Integer | 40 | 41 | --- 42 | 43 | ## Develop By Muhammad Zakir Ramadhan -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from core.router import Router 3 | from flask_cors import CORS, cross_origin 4 | 5 | app = Flask(__name__) 6 | cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) 7 | Router.run(app) -------------------------------------------------------------------------------- /core/application.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import pytesseract 4 | import matplotlib.pyplot as plt 5 | from PIL import Image 6 | import requests 7 | from flask import request 8 | from werkzeug.utils import secure_filename 9 | import re 10 | 11 | def allowed_image(filename): 12 | allow = ["JPEG", "JPG", "PNG"] 13 | 14 | if not "." in filename: 15 | return False 16 | 17 | ext = filename.rsplit(".", 1)[1] 18 | if ext.upper() in allow: 19 | return True 20 | else: 21 | return False 22 | 23 | def word_to_number_converter(word): 24 | word_dict = { 25 | "L" : "1", 26 | 'l' : "1", 27 | 'O' : "0", 28 | 'o' : "0", 29 | '?' : "7" 30 | } 31 | 32 | res = '' 33 | for letter in word: 34 | if letter in word_dict: 35 | res += word_dict[letter] 36 | else: 37 | res += letter 38 | return res 39 | 40 | def match_ktp_nik(): 41 | if request.method == "POST": 42 | image = request.files["ktp"] 43 | noktp = request.form['nik'] 44 | 45 | if image.filename == "": 46 | return { 47 | 'success':False, 48 | 'message':'Empty Fields!' 49 | } 50 | 51 | if noktp == "": 52 | return { 53 | 'success':False, 54 | 'message':'Empty Fields!' 55 | } 56 | 57 | if allowed_image(image.filename): 58 | img = cv2.imdecode(np.fromstring(request.files['ktp'].read(), np.uint8), cv2.IMREAD_UNCHANGED) 59 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 60 | th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC) 61 | result = pytesseract.image_to_string((threshed), lang="ind") 62 | result.replace('\n', ' ') 63 | match = False 64 | 65 | if 'NIK' in result: 66 | 67 | for word in result.split("\n"): 68 | if "NIK" in word: 69 | word = word.split(':') 70 | nik = word_to_number_converter(word[-1].replace(" ", "")) 71 | 72 | if noktp == nik: 73 | match = True 74 | else: 75 | match = False 76 | 77 | return { 78 | 'success':True, 79 | 'result': { 80 | 'match':match, 81 | 'nik':noktp 82 | } 83 | } 84 | else: 85 | return { 86 | 'success':False, 87 | 'message':'KTP Tidak Terdeteksi' 88 | } 89 | else: 90 | return { 91 | 'success':False, 92 | 'message':'Extension Not Allowed!' 93 | } 94 | else: 95 | return { 96 | 'success':False, 97 | 'message':'Method Not Allowed!' 98 | } 99 | 100 | def valid_ktp(): 101 | if request.method == "POST": 102 | image = request.files["ktp"] 103 | 104 | if image.filename == "": 105 | return { 106 | 'success':False, 107 | 'message':'Empty Fields!' 108 | } 109 | 110 | if allowed_image(image.filename): 111 | img = cv2.imdecode(np.fromstring(request.files['ktp'].read(), np.uint8), cv2.IMREAD_UNCHANGED) 112 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 113 | th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC) 114 | result = pytesseract.image_to_string((threshed), lang="ind") 115 | result.replace('\n', ' ') 116 | valid = False 117 | 118 | if 'NIK' in result: 119 | valid = True 120 | else: 121 | valid = False 122 | 123 | return { 124 | 'success':True, 125 | 'result':{ 126 | 'valid':valid 127 | } 128 | } 129 | 130 | else: 131 | return { 132 | 'success':False, 133 | 'message':'Extension Not Allowed!' 134 | } 135 | else: 136 | return { 137 | 'success':False, 138 | 'message':'Method Not Allowed!' 139 | } 140 | 141 | def extract_ktp(): 142 | if request.method == "POST": 143 | image = request.files["ktp"] 144 | 145 | if image.filename == "": 146 | return { 147 | 'success':False, 148 | 'message':'Empty Fields!' 149 | } 150 | 151 | if allowed_image(image.filename): 152 | img = cv2.imdecode(np.fromstring(request.files['ktp'].read(), np.uint8), cv2.IMREAD_UNCHANGED) 153 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 154 | th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC) 155 | result = pytesseract.image_to_string((threshed), lang="ind") 156 | result.replace('\n', ' ') 157 | 158 | if 'NIK' in result and 'Nama' in result and 'Lahir' in result and 'Jenis kelamin' in result and 'Alamat' in result: 159 | for word in result.split("\n"): 160 | if "NIK" in word: 161 | word = word.split(':') 162 | nik = word_to_number_converter(word[-1].replace(" ", "")) 163 | continue 164 | 165 | if "Nama" in word: 166 | word = word.split(':') 167 | nama = word[-1] 168 | 169 | if "Lahir" in word: 170 | word = word.split(':') 171 | tgl_lahir = re.search("([0-9]{2}\-[0-9]{2}\-[0-9]{4})", word[-1])[0] 172 | tmp_lahir = word[-1].replace(tgl_lahir, '') 173 | continue 174 | 175 | if 'Darah' in word: 176 | jenis_kelamin = re.search("(LAKI-LAKI|LAKI|LELAKI|PEREMPUAN)", word)[0] 177 | word = word.split(':') 178 | 179 | return { 180 | 'success':True, 181 | 'message':'Valid KTP', 182 | 'data': { 183 | 'nik':nik, 184 | 'nama':nama, 185 | 'tanggal_lahir': tgl_lahir, 186 | 'tempat_lahir':tmp_lahir, 187 | 'jenis_kelamin':jenis_kelamin 188 | } 189 | } 190 | 191 | elif 'NIK' in result: 192 | return { 193 | 'success':True, 194 | 'message':'Foto KTP BLur/Rusak/Low Resolution' 195 | } 196 | else: 197 | return { 198 | 'success':False, 199 | 'message':'KTP Tidak Terdeteksi' 200 | } 201 | else: 202 | return { 203 | 'success':False, 204 | 'message':'Extension Not Allowed!' 205 | } 206 | else: 207 | return { 208 | 'success':False, 209 | 'message':'mMthod Not Allowed!' 210 | } -------------------------------------------------------------------------------- /core/router.py: -------------------------------------------------------------------------------- 1 | import core.application as model 2 | 3 | class Router: 4 | @staticmethod 5 | def run(app): 6 | @app.route('/') 7 | def home(): 8 | return { 9 | 'message':'Tugas Kelompok Dengan Python, Studi Kasus Extract KTP & Validasi KTP Dengan Metode OCR', 10 | 'developer':'Muhammad Zakir Ramadhan' 11 | } 12 | 13 | @app.route('/api/extract_ktp', methods=['POST']) 14 | def extract(): 15 | return model.extract_ktp() 16 | 17 | @app.route('/api/valid', methods=['POST']) 18 | def valid(): 19 | return model.valid_ktp() 20 | 21 | @app.route('/api/match', methods=['POST']) 22 | def match(): 23 | return model.match_ktp_nik() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==2.4.2 2 | beautifulsoup4==4.9.1 3 | certifi==2020.4.5.2 4 | chardet==3.0.4 5 | click==7.1.2 6 | colorama==0.4.3 7 | Flask==1.1.2 8 | gunicorn==20.0.4 9 | idna==2.9 10 | isort==4.3.21 11 | itsdangerous==1.1.0 12 | Jinja2==2.11.2 13 | lazy-object-proxy==1.4.3 14 | MarkupSafe==1.1.1 15 | mccabe==0.6.1 16 | pylint==2.5.3 17 | requests==2.23.0 18 | six==1.15.0 19 | soupsieve==2.0.1 20 | toml==0.10.1 21 | urllib3==1.25.9 22 | Werkzeug==1.0.1 23 | wrapt==1.12.1 24 | Flask-Cors==3.0.9 25 | matplotlib==3.3.3 26 | opencv-python==4.4.0.46 27 | pytesseract==0.3.6 --------------------------------------------------------------------------------