anh-khoa-nguyen commited on
Commit
e155984
·
1 Parent(s): 591424d

Initial commit of CCCD OCR API

Browse files
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/LandifyOCR.iml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="Python 3.8 (LandifyOCR)" jdkType="Python SDK" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="ignoredPackages">
6
+ <value>
7
+ <list size="10">
8
+ <item index="0" class="java.lang.String" itemvalue="psycopg2" />
9
+ <item index="1" class="java.lang.String" itemvalue="postgis" />
10
+ <item index="2" class="java.lang.String" itemvalue="mypy_extensions" />
11
+ <item index="3" class="java.lang.String" itemvalue="pathspec" />
12
+ <item index="4" class="java.lang.String" itemvalue="pyflakes" />
13
+ <item index="5" class="java.lang.String" itemvalue="mccabe" />
14
+ <item index="6" class="java.lang.String" itemvalue="black" />
15
+ <item index="7" class="java.lang.String" itemvalue="isort" />
16
+ <item index="8" class="java.lang.String" itemvalue="pycodestyle" />
17
+ <item index="9" class="java.lang.String" itemvalue="flake8" />
18
+ </list>
19
+ </value>
20
+ </option>
21
+ </inspection_tool>
22
+ </profile>
23
+ </component>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.8 (LandifyOCR)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (LandifyOCR)" project-jdk-type="Python SDK" />
7
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/LandifyOCR.iml" filepath="$PROJECT_DIR$/.idea/LandifyOCR.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
README.md CHANGED
@@ -1,11 +1,15 @@
1
  ---
2
- title: Landify Cccd Ocr
3
- emoji: 👁
4
- colorFrom: indigo
5
- colorTo: yellow
6
  sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: Vietnamese Citizen ID OCR
3
+ emoji: 💳
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ python_version: 3.8
8
+ app_port: 7860
9
  ---
10
 
11
+ # Vietnamese Citizen ID (CCCD) OCR API
12
+
13
+ Đây là một API sử dụng FastAPI để trích xuất thông tin từ ảnh Căn cước công dân Việt Nam.
14
+
15
+ Sử dụng endpoint `/docs` để xem tài liệu và thử nghiệm API.
app.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import datetime
3
+ import os
4
+ import re
5
+ import time
6
+ import uuid
7
+
8
+ import cv2
9
+ from fastapi import FastAPI, File, UploadFile, HTTPException
10
+ from pydantic import BaseModel
11
+ from typing import Optional
12
+
13
+ # Import lớp Extractor từ thư mục core
14
+ from core.extractor import Extractor
15
+
16
+ # --- Khởi tạo ---
17
+
18
+ # Khởi tạo ứng dụng FastAPI
19
+ app = FastAPI(
20
+ title="CCCD Extraction API",
21
+ description="Một microservice để trích xuất thông tin từ Căn cước công dân Việt Nam.",
22
+ version="1.0.0"
23
+ )
24
+
25
+ # Đường dẫn để lưu trữ file upload
26
+ UPLOAD_DIR = "uploads"
27
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
28
+
29
+ # Khởi tạo một lần duy nhất đối tượng Extractor để tái sử dụng
30
+ # Điều này giúp load model một lần và tăng tốc độ xử lý cho các request sau
31
+ try:
32
+ idcard_extractor = Extractor()
33
+ print("CCCD Extractor loaded successfully.")
34
+ except Exception as e:
35
+ print(f"Error loading CCCD Extractor: {e}")
36
+ idcard_extractor = None
37
+
38
+
39
+ # --- Định nghĩa Model cho Request và Response ---
40
+
41
+ # Model cho request nếu gửi ảnh dạng base64
42
+ class ImageRequest(BaseModel):
43
+ image_base64: str
44
+
45
+
46
+ # Model cho response trả về
47
+ class ExtractionResponse(BaseModel):
48
+ ID_number: Optional[str] = None
49
+ Name: Optional[str] = None
50
+ Date_of_birth: Optional[str] = None
51
+ Gender: Optional[str] = None
52
+ Nationality: Optional[str] = None
53
+ Place_of_origin: Optional[str] = None
54
+ Place_of_residence: Optional[str] = None
55
+ elapsed: float
56
+
57
+
58
+ # --- Xây dựng API Endpoint ---
59
+
60
+ @app.get("/")
61
+ def read_root():
62
+ return {"message": "Welcome to the CCCD Extraction API. Use the /extract/ endpoint to process an image."}
63
+
64
+
65
+ @app.post("/extract/", response_model=ExtractionResponse, tags=["CCCD Extraction"])
66
+ async def extract_id_card_info(file: UploadFile = File(...)):
67
+ """
68
+ Nhận một file ảnh CCCD, trích xuất thông tin và trả về.
69
+ """
70
+ if not idcard_extractor:
71
+ raise HTTPException(status_code=500, detail="OCR Extractor is not available.")
72
+
73
+ # --- 1. Lưu file ảnh được upload ---
74
+ # Tạo tên file ngẫu nhiên và an toàn để tránh trùng lặp
75
+ file_extension = os.path.splitext(file.filename)[1]
76
+ random_filename = f"{uuid.uuid4()}{file_extension}"
77
+ file_path = os.path.join(UPLOAD_DIR, random_filename)
78
+
79
+ try:
80
+ # Đọc nội dung file và lưu lại
81
+ with open(file_path, "wb") as buffer:
82
+ buffer.write(await file.read())
83
+ except Exception as e:
84
+ raise HTTPException(status_code=500, detail=f"Could not save uploaded file: {e}")
85
+
86
+ # --- 2. Xử lý ảnh và trích xuất thông tin (logic từ Django view) ---
87
+ start_time = time.time()
88
+ try:
89
+ frame = cv2.imread(file_path)
90
+ if frame is None:
91
+ raise HTTPException(status_code=400, detail="Invalid image file.")
92
+
93
+ # Bước 1: Dùng PaddleOCR để phát hiện các vùng văn bản
94
+ annotations = idcard_extractor.Detection(frame)
95
+
96
+ info = {}
97
+
98
+ # Tìm số CCCD trước tiên
99
+ for box in annotations:
100
+ text_detected = box[1][0]
101
+ if re.search(r'\d{9,12}', text_detected):
102
+ # Tách số ra khỏi chuỗi nhiễu
103
+ id_number = re.search(r'\d{9,12}', text_detected).group(0)
104
+ info['ID_number'] = id_number
105
+ info['ID_number_box'] = box[0]
106
+ break
107
+
108
+ if 'ID_number' not in info:
109
+ raise HTTPException(status_code=400, detail="Could not detect an ID number in the image.")
110
+
111
+ # Bước 2: Dùng VietOCR để nhận dạng các trường thông tin còn lại
112
+ extracted_result = []
113
+ for box in annotations:
114
+ # Bỏ qua vùng chứa số ID đã xử lý
115
+ if re.search(r'\d{9,12}', box[1][0]):
116
+ continue
117
+
118
+ top_left = (int(box[0][0][0]), int(box[0][0][1]))
119
+ top_right = (int(box[0][1][0]), int(box[0][1][1]))
120
+ bottom_right = (int(box[0][2][0]), int(box[0][2][1]))
121
+ bottom_left = (int(box[0][3][0]), int(box[0][3][1]))
122
+
123
+ # Warp và nhận dạng
124
+ result_text, _ = idcard_extractor.WarpAndRec(frame, top_left, top_right, bottom_right, bottom_left)
125
+ extracted_result.append((result_text, box[0])) # Lưu cả text và bounding box
126
+
127
+ # Bước 3: Tổng hợp thông tin
128
+ final_info = idcard_extractor.GetInformationAndSave(extracted_result, info['ID_number'], info['ID_number_box'])
129
+
130
+ elapsed = time.time() - start_time
131
+ final_info["elapsed"] = round(elapsed, 2)
132
+
133
+ # Xóa file tạm sau khi xử lý xong
134
+ os.remove(file_path)
135
+
136
+ return final_info
137
+
138
+ except Exception as e:
139
+ # Nếu có lỗi, cũng xóa file tạm
140
+ if os.path.exists(file_path):
141
+ os.remove(file_path)
142
+ raise HTTPException(status_code=500, detail=f"An error occurred during processing: {str(e)}")
core/__init__.py ADDED
File without changes
core/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (129 Bytes). View file
 
core/__pycache__/extractor.cpython-38.pyc ADDED
Binary file (6.91 kB). View file
 
core/extractor.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import cv2
5
+ import time
6
+ import threading
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ from PIL import Image
10
+ from paddleocr import PaddleOCR
11
+ from vietocr.tool.predictor import Predictor
12
+ from vietocr.tool.config import Cfg
13
+
14
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
15
+
16
+ ocr = None
17
+ detector = None
18
+
19
+
20
+ class Extractor:
21
+
22
+ def __init__(self):
23
+
24
+ self.config = Cfg.load_config_from_name('vgg_seq2seq')
25
+ self.config['weights'] = os.path.join(CURRENT_DIR, "seq2seqocr.pth")
26
+ self.config['cnn']['pretrained'] = False
27
+ self.config['device'] = 'cpu'
28
+
29
+ if (ocr == None):
30
+ self.ocr = PaddleOCR(lang='en')
31
+ else:
32
+ self.ocr = ocr
33
+ if (detector == None):
34
+ self.detector = Predictor(self.config)
35
+ else:
36
+ self.detector = detector
37
+
38
+ # result = {'ID_number':'',
39
+ # 'Name':'',
40
+ # 'Date_of_birth':'',
41
+ # 'Gender':'',
42
+ # 'Nationality':'',
43
+ # 'Place_of_origin':'',
44
+ # 'Place_of_residence':''}
45
+
46
+ ####################################################################################################
47
+
48
+ def Detection(self, frame):
49
+ annotations = self.ocr.ocr(frame, rec=True, cls=False)
50
+ return annotations[0]
51
+
52
+ ####################################################################################################
53
+
54
+ def WarpAndSave(self, frame, fileName, top_left, top_right, bottom_right, bottom_left):
55
+
56
+ w, h, cn = frame.shape
57
+ padding = 4.0
58
+ padding = int(padding * w / 640)
59
+
60
+ # All points are in format [cols, rows]
61
+ pt_A = top_left[0], top_left[1]
62
+ pt_B = bottom_left[0], bottom_left[1]
63
+ pt_C = bottom_right[0], bottom_right[1]
64
+ pt_D = top_right[0], top_right[1]
65
+
66
+ # Here, I have used L2 norm. You can use L1 also.
67
+ width_AD = np.sqrt(((pt_A[0] - pt_D[0]) ** 2) + ((pt_A[1] - pt_D[1]) ** 2))
68
+ width_BC = np.sqrt(((pt_B[0] - pt_C[0]) ** 2) + ((pt_B[1] - pt_C[1]) ** 2))
69
+ maxWidth = max(int(width_AD), int(width_BC))
70
+
71
+ height_AB = np.sqrt(((pt_A[0] - pt_B[0]) ** 2) + ((pt_A[1] - pt_B[1]) ** 2))
72
+ height_CD = np.sqrt(((pt_C[0] - pt_D[0]) ** 2) + ((pt_C[1] - pt_D[1]) ** 2))
73
+ maxHeight = max(int(height_AB), int(height_CD))
74
+
75
+ input_pts = np.float32([pt_A, pt_B, pt_C, pt_D])
76
+ output_pts = np.float32([[0, 0],
77
+ [0, maxHeight - 1],
78
+ [maxWidth - 1, maxHeight - 1],
79
+ [maxWidth - 1, 0]])
80
+
81
+ # Compute the perspective transform M
82
+ M = cv2.getPerspectiveTransform(input_pts, output_pts)
83
+
84
+ matWarped = cv2.warpPerspective(frame, M, (maxWidth, maxHeight), flags=cv2.INTER_LINEAR)
85
+ cv2.imwrite(fileName, matWarped)
86
+
87
+ return True
88
+
89
+ ####################################################################################################
90
+
91
+ def WarpAndRec(self, frame, top_left, top_right, bottom_right, bottom_left):
92
+ w, h, cn = frame.shape
93
+ padding = 4.0
94
+ padding = int(padding * w / 640)
95
+
96
+ box = []
97
+ # All points are in format [cols, rows]
98
+ pt_A = top_left[0] - padding, top_left[1] - padding
99
+ pt_B = bottom_left[0] - padding, bottom_left[1] + padding
100
+ pt_C = bottom_right[0] + padding, bottom_right[1] + padding
101
+ pt_D = top_right[0] + padding, top_right[1] - padding
102
+
103
+ # Here, I have used L2 norm. You can use L1 also.
104
+ width_AD = np.sqrt(((pt_A[0] - pt_D[0]) ** 2) + ((pt_A[1] - pt_D[1]) ** 2))
105
+ width_BC = np.sqrt(((pt_B[0] - pt_C[0]) ** 2) + ((pt_B[1] - pt_C[1]) ** 2))
106
+ maxWidth = max(int(width_AD), int(width_BC))
107
+
108
+ height_AB = np.sqrt(((pt_A[0] - pt_B[0]) ** 2) + ((pt_A[1] - pt_B[1]) ** 2))
109
+ height_CD = np.sqrt(((pt_C[0] - pt_D[0]) ** 2) + ((pt_C[1] - pt_D[1]) ** 2))
110
+ maxHeight = max(int(height_AB), int(height_CD))
111
+
112
+ input_pts = np.float32([pt_A, pt_B, pt_C, pt_D])
113
+ output_pts = np.float32([[0, 0],
114
+ [0, maxHeight - 1],
115
+ [maxWidth - 1, maxHeight - 1],
116
+ [maxWidth - 1, 0]])
117
+
118
+ # Compute the perspective transform M
119
+ M = cv2.getPerspectiveTransform(input_pts, output_pts)
120
+
121
+ matWarped = cv2.warpPerspective(frame, M, (maxWidth, maxHeight), flags=cv2.INTER_LINEAR)
122
+ # cv2.imwrite(fileName, matWarped)
123
+
124
+ s = self.detector.predict(Image.fromarray(matWarped))
125
+
126
+ box.append(pt_A)
127
+ box.append(pt_D)
128
+ box.append(pt_C)
129
+ box.append(pt_B)
130
+
131
+ return [s, box]
132
+
133
+ ####################################################################################################
134
+
135
+ def GetInformationAndSave(self, _results, _idnumber, _idnumberbox):
136
+ print("---------------------------------")
137
+ print(_results)
138
+ # string = '{"ID_number": "09219802508", "Name": "", "Date_of_birth": "", "Gender": "", "Nationality": "", "Place_of_origin": "", "Place_of_residence": "", "ID_number_box": [[208.0, 171.0], [495.0, 177.0], [495.0, 201.0], [208.0, 195.0]]}'
139
+ # result = json.loads(string)
140
+
141
+ result = {}
142
+ result['ID_number'] = _idnumber
143
+ result['Name'] = ''
144
+ result['Date_of_birth'] = ''
145
+ result['Gender'] = ''
146
+ result['Nationality'] = ''
147
+ result['Place_of_origin'] = ''
148
+ result['Place_of_residence'] = ''
149
+ result['ID_number_box'] = _idnumberbox
150
+
151
+ regex_dob = r'[0-9][0-9]/[0-9][0-9]'
152
+ regex_residence = r'[0-9][0-9]/[0-9][0-9]/|[0-9]{4,10}|Date|Demo|Dis|Dec|Dale|fer|ting|gical|ping|exp|ver|pate|cond|trị|đến|không|Không|Có|Pat|ter|ity'
153
+
154
+ for i, res in enumerate(_results):
155
+ s = res[0]
156
+
157
+ print(s)
158
+ if re.search(r'tên|name', s):
159
+ # result['ID_number'] = result[i+1].split(':|;|,|\\.|\s+')[-1].strip()
160
+ # ID_number = result[i+1] if re.search(r'[0-9][0-9][0-9]',(re.split(r':|[.]|\s+',result[i+1][0]))[-1].strip()) else (result[i+2] if re.search(r'[0-9][0-9][0-9]',result[i+2][0]) else result[i+3])
161
+ # result['ID_number'] = (re.split(r':|[.]|\s+',ID_number[0]))[-1].strip()
162
+ # result['ID_number_box'] = ID_number[1]
163
+
164
+ Name = _results[i + 1] if (not re.search(r'[0-9]', _results[i + 1][0])) else _results[i + 2]
165
+ result['Name'] = Name[0].title()
166
+ result['Name_box'] = Name[1] if Name[1] else []
167
+
168
+ if (result['Date_of_birth'] == ''):
169
+ DOB = _results[i - 2] if re.search(regex_dob, _results[i - 2][0]) else []
170
+ result['Date_of_birth'] = (re.split(r':|\s+', DOB[0]))[-1].strip() if DOB else ''
171
+ result['Date_of_birth_box'] = DOB[1] if DOB else []
172
+ continue
173
+
174
+ if re.search(r'sinh|birth|bith', s) and (not result['Date_of_birth']):
175
+ if re.search(regex_dob, s):
176
+ DOB = _results[i]
177
+
178
+ elif re.search(regex_dob, _results[i - 1][0]):
179
+ DOB = _results[i - 1]
180
+
181
+ elif re.search(regex_dob, _results[i + 1][0]):
182
+ DOB = _results[i + 1]
183
+
184
+ else:
185
+ DOB = []
186
+
187
+ result['Date_of_birth'] = (re.split(r':|\s+', DOB[0]))[-1].strip() if DOB else ''
188
+ result['Date_of_birth_box'] = DOB[1] if DOB else []
189
+
190
+ if re.search(r"Việt Nam", _results[i + 1][0]):
191
+ result['Nationality'] = 'Việt Nam'
192
+ result['Nationality_box'] = _results[i + 1][1]
193
+
194
+ continue
195
+
196
+ if re.search(r'Giới|Sex', s):
197
+ Gender = _results[i]
198
+ result['Gender'] = 'Nữ' if re.search(r'Nữ|nữ', Gender[0]) else 'Nam'
199
+ result['Gender_box'] = Gender[1] if Gender[1] else []
200
+ # continue
201
+
202
+ if re.search(r'Quốc|tịch|Nat', s):
203
+ if (not re.search(r'ty|ing', re.split(r':|,|[.]|ty|tịch', s)[-1].strip()) and (
204
+ len(re.split(r':|,|[.]|ty|tịch', s)[-1].strip()) >= 3)):
205
+ Nationality = _results[i]
206
+
207
+ elif not re.search(r'[0-9][0-9]/[0-9][0-9]/', _results[i + 1][0]):
208
+ Nationality = _results[i + 1]
209
+
210
+ else:
211
+ Nationality = _results[i - 1]
212
+
213
+ result['Nationality'] = re.split(r':|-|,|[.]|ty|[0-9]|tịch', Nationality[0])[-1].strip().title()
214
+ result['Nationality_box'] = Nationality[1] if Nationality[1] else []
215
+
216
+ for s in re.split(r'\s+', result['Nationality']):
217
+ if len(s) < 3:
218
+ result['Nationality'] = re.split(s, result['Nationality'])[-1].strip().title()
219
+ if re.search(r'Nam', result['Nationality']):
220
+ result['Nationality'] = 'Việt Nam'
221
+
222
+ continue
223
+
224
+ if re.search(r'Quê|origin|ongin|ngin|orging', s):
225
+ PlaceOfOrigin = [_results[i], _results[i + 1]] if not re.search(r'[0-9]{4}', _results[i + 1][0]) else []
226
+ if PlaceOfOrigin:
227
+ if len(re.split(r':|;|of|ging|gin|ggong', PlaceOfOrigin[0][0])[-1].strip()) > 2:
228
+ result['Place_of_origin'] = (
229
+ (re.split(r':|;|of|ging|gin|ggong', PlaceOfOrigin[0][0]))[-1].strip() + ', ' +
230
+ PlaceOfOrigin[1][0])
231
+ else:
232
+ result['Place_of_origin'] = PlaceOfOrigin[1][0]
233
+ result['Place_of_origin_box'] = PlaceOfOrigin[1][1]
234
+ continue
235
+
236
+ if re.search(r'Nơi|trú|residence', s):
237
+ vals2 = "" if (i + 2 > len(_results) - 1) else _results[i + 2] if len(_results[i + 2][0]) > 5 else \
238
+ _results[-1]
239
+ vals3 = "" if (i + 3 > len(_results) - 1) else _results[i + 3] if len(_results[i + 3][0]) > 5 else \
240
+ _results[-1]
241
+
242
+ if ((re.split(r':|;|residence|ence|end', s))[-1].strip() != ''):
243
+
244
+ if (vals2 != '' and not re.search(regex_residence, vals2[0])):
245
+ PlaceOfResidence = [_results[i], vals2]
246
+ elif (vals3 != '' and not re.search(regex_residence, vals3[0])):
247
+ PlaceOfResidence = [_results[i], vals3]
248
+ elif not re.search(regex_residence, _results[-1][0]):
249
+ PlaceOfResidence = [_results[i], _results[-1]]
250
+ else:
251
+ PlaceOfResidence = [_results[-1], []]
252
+
253
+ else:
254
+ PlaceOfResidence = [vals2, []] if (vals2 and not re.search(regex_residence, vals2[0])) else [
255
+ _results[-1], []]
256
+
257
+ print('PlaceOfResidence: {}'.format(PlaceOfResidence))
258
+ if PlaceOfResidence[1]:
259
+ result['Place_of_residence'] = re.split(r':|;|residence|sidencs|ence|end', PlaceOfResidence[0][0])[
260
+ -1].strip() + ' ' + str(PlaceOfResidence[1][0]).strip()
261
+ result['Place_of_residence_box'] = PlaceOfResidence[1][1]
262
+
263
+ else:
264
+ result['Place_of_residence'] = PlaceOfResidence[0][0]
265
+ result['Place_of_residence_box'] = PlaceOfResidence[0][1] if PlaceOfResidence else []
266
+ continue
267
+
268
+ elif (i == len(_results) - 1):
269
+ if result['Place_of_residence'] == '':
270
+ if not re.search(regex_residence, _results[-1][0]):
271
+ PlaceOfResidence = _results[-1]
272
+ elif not re.search(regex_residence, _results[-2][0]):
273
+ PlaceOfResidence = _results[-2]
274
+ else:
275
+ PlaceOfResidence = []
276
+
277
+ result['Place_of_residence'] = PlaceOfResidence[0] if PlaceOfResidence else ''
278
+ result['Place_of_residence_box'] = PlaceOfResidence[1] if PlaceOfResidence else []
279
+ if result['Gender'] == '':
280
+ result['Gender_box'] = []
281
+ if result['Nationality'] == '':
282
+ result['Nationality_box'] = []
283
+ if result['Name'] == '':
284
+ result['Name_box'] = []
285
+ if result['Date_of_birth'] == '':
286
+ result['Date_of_birth_box'] = []
287
+ if result['Place_of_origin'] == '':
288
+ result['Place_of_origin_box'] = []
289
+
290
+ else:
291
+ continue
292
+
293
+ with open('extracted_infomation.json', 'w', encoding='utf-8') as f:
294
+ f.write(json.dumps(result, indent=4, ensure_ascii=False))
295
+ f.close()
296
+
297
+ return result
298
+
299
+
300
+ ####################################################################################################
301
+
302
+ idcard_extractor = Extractor()
303
+ # info = idcard_extractor.GetInformationAndSave("extracted_result")
304
+ # print(info)
305
+
306
+ if __name__ == '__main__':
307
+ img_path = './20211019_090832.jpg'
308
+ frame = cv2.imread(img_path)
309
+ # annotations = idcard_extractor.Detection(img_path)
310
+ # extracted_result=[]
311
+
312
+ # threads = []
313
+ # for i, box in enumerate(annotations):
314
+
315
+ # top_left = (int(box[0][0]), int(box[0][1]))
316
+ # top_right = (int(box[1][0]), int(box[1][1]))
317
+ # bottom_right = (int(box[2][0]), int(box[2][1]))
318
+ # bottom_left = (int(box[3][0]), int(box[3][1]))
319
+
320
+ # t = ThreadWithReturnValue(target=idcard_extractor.WarpAndRec, args=(frame,top_left, top_right, bottom_right, bottom_left))
321
+ # threads.append(t)
322
+
323
+ # for t in threads:
324
+ # t.start()
325
+
326
+ # for t in threads:
327
+ # extracted_result.append(t.join())
328
+
329
+ info = idcard_extractor.GetInformationAndSave("extracted_result")
330
+ print(info)
core/seq2seqocr.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0921503a41375a0584268e23ef3d414ea478a8fe8777865c7745d38f2d0bc5db
3
+ size 89575371
extracted_infomation.json ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ID_number": "077204004336",
3
+ "Name": "Nguyễn Anh Khoa",
4
+ "Date_of_birth": "04/09/2004",
5
+ "Gender": "Nam",
6
+ "Nationality": "",
7
+ "Place_of_origin": "Quảng Nam",
8
+ "Place_of_residence": "Bình Châu, Xuyên Mộc, Bà Rịa - Vũng Tàu",
9
+ "ID_number_box": [
10
+ [
11
+ 772.0,
12
+ 560.0
13
+ ],
14
+ [
15
+ 1358.0,
16
+ 560.0
17
+ ],
18
+ [
19
+ 1358.0,
20
+ 616.0
21
+ ],
22
+ [
23
+ 772.0,
24
+ 616.0
25
+ ]
26
+ ],
27
+ "Name_box": [
28
+ [
29
+ 586.0,
30
+ 708.0
31
+ ],
32
+ [
33
+ 1232.0,
34
+ 708.0
35
+ ],
36
+ [
37
+ 1232.0,
38
+ 770.0
39
+ ],
40
+ [
41
+ 586.0,
42
+ 770.0
43
+ ]
44
+ ],
45
+ "Date_of_birth_box": [
46
+ [
47
+ 1096.0,
48
+ 789.0
49
+ ],
50
+ [
51
+ 1388.0,
52
+ 789.0
53
+ ],
54
+ [
55
+ 1388.0,
56
+ 833.0
57
+ ],
58
+ [
59
+ 1096.0,
60
+ 833.0
61
+ ]
62
+ ],
63
+ "Gender_box": [
64
+ [
65
+ 586.0,
66
+ 854.0
67
+ ],
68
+ [
69
+ 1495.0,
70
+ 854.0
71
+ ],
72
+ [
73
+ 1495.0,
74
+ 902.0
75
+ ],
76
+ [
77
+ 586.0,
78
+ 902.0
79
+ ]
80
+ ],
81
+ "Nationality_box": [],
82
+ "Place_of_origin_box": [
83
+ [
84
+ 586.0,
85
+ 991.0
86
+ ],
87
+ [
88
+ 896.0,
89
+ 991.0
90
+ ],
91
+ [
92
+ 896.0,
93
+ 1041.0
94
+ ],
95
+ [
96
+ 586.0,
97
+ 1041.0
98
+ ]
99
+ ],
100
+ "Place_of_residence_box": [
101
+ [
102
+ 586.0,
103
+ 1122.0
104
+ ],
105
+ [
106
+ 1665.0,
107
+ 1120.0
108
+ ],
109
+ [
110
+ 1665.0,
111
+ 1176.0
112
+ ],
113
+ [
114
+ 586.0,
115
+ 1179.0
116
+ ]
117
+ ]
118
+ }
requirements.txt ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.7.0
2
+ anyio==4.5.2
3
+ astor==0.8.1
4
+ attrdict==2.0.1
5
+ babel==2.17.0
6
+ bce-python-sdk==0.9.45
7
+ beautifulsoup4==4.13.5
8
+ blinker==1.8.2
9
+ cachetools==5.5.2
10
+ certifi==2025.8.3
11
+ charset-normalizer==3.4.3
12
+ click==8.1.8
13
+ colorama==0.4.6
14
+ contourpy==1.1.1
15
+ cssselect==1.2.0
16
+ cssutils==2.11.1
17
+ cycler==0.12.1
18
+ Cython==3.1.3
19
+ decorator==5.2.1
20
+ einops==0.2.0
21
+ et_xmlfile==2.0.0
22
+ exceptiongroup==1.3.0
23
+ fastapi==0.116.1
24
+ filelock==3.16.1
25
+ fire==0.7.1
26
+ Flask==3.0.3
27
+ flask-babel==4.0.0
28
+ fonttools==4.57.0
29
+ fsspec==2025.3.0
30
+ future==1.0.0
31
+ gdown==4.4.0
32
+ h11==0.16.0
33
+ httpcore==1.0.9
34
+ httptools==0.6.4
35
+ httpx==0.28.1
36
+ idna==3.10
37
+ imageio==2.35.1
38
+ imgaug==0.4.0
39
+ importlib_metadata==8.5.0
40
+ itsdangerous==2.2.0
41
+ Jinja2==3.1.6
42
+ kiwisolver==1.4.7
43
+ lmdb==1.7.3
44
+ lxml==6.0.1
45
+ MarkupSafe==2.1.5
46
+ matplotlib==3.6.3
47
+ more-itertools==10.5.0
48
+ mpmath==1.3.0
49
+ networkx==3.1
50
+ numpy==1.19.3
51
+ opencv-python-headless==4.4.0.44
52
+ openpyxl==3.1.5
53
+ opt-einsum==3.3.0
54
+ packaging==25.0
55
+ paddleocr==2.7.3
56
+ paddlepaddle==2.6.1
57
+ pandas==1.4.4
58
+ pdf2docx==0.5.7
59
+ Pillow==9.5.0
60
+ prefetch-generator==1.0.1
61
+ premailer==3.10.0
62
+ protobuf==3.20.2
63
+ psutil==7.0.0
64
+ pyclipper==1.3.0.post6
65
+ pycryptodome==3.23.0
66
+ pydantic==2.10.6
67
+ pydantic_core==2.27.2
68
+ PyMuPDF==1.24.11
69
+ pyparsing==3.1.4
70
+ PySocks==1.7.1
71
+ python-dateutil==2.9.0.post0
72
+ python-docx==1.1.2
73
+ python-dotenv==1.0.1
74
+ python-multipart==0.0.20
75
+ pytz==2025.2
76
+ PyWavelets==1.4.1
77
+ PyYAML==6.0.2
78
+ rapidfuzz==3.9.7
79
+ rarfile==4.2
80
+ requests==2.32.4
81
+ scikit-image==0.19.3
82
+ scipy==1.9.3
83
+ shapely==2.0.7
84
+ six==1.17.0
85
+ sniffio==1.3.1
86
+ soupsieve==2.7
87
+ starlette==0.44.0
88
+ sympy==1.13.3
89
+ termcolor==2.4.0
90
+ tifffile==2023.7.10
91
+ torch==2.2.2
92
+ torchvision==0.17.2
93
+ tqdm==4.67.1
94
+ typing_extensions==4.13.2
95
+ urllib3==2.2.3
96
+ uvicorn==0.33.0
97
+ vietocr==0.3.11
98
+ visualdl==2.5.3
99
+ watchfiles==0.24.0
100
+ websockets==13.1
101
+ Werkzeug==3.0.6
102
+ zipp==3.20.2