Spaces:
Sleeping
Sleeping
initiated
Browse files- app.py +85 -0
- card_number_extractor.tflite +3 -0
- credit_card_number_detector.tflite +3 -0
- extractor.py +159 -0
- requirements.txt.txt +70 -0
app.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
from PIL import Image, ImageDraw
|
5 |
+
# import imutils
|
6 |
+
# import easyocr
|
7 |
+
# import os
|
8 |
+
# import pathlib
|
9 |
+
# import platform
|
10 |
+
# from xyxy_converter import yolov5_to_image_coordinates
|
11 |
+
# import shutil
|
12 |
+
from extractor import get_card_xy, get_digit
|
13 |
+
|
14 |
+
# system_platform = platform.system()
|
15 |
+
# if system_platform == 'Windows': pathlib.PosixPath = pathlib.WindowsPath
|
16 |
+
|
17 |
+
# CUR_DIR = os.getcwd()
|
18 |
+
# YOLO_PATH = f"{CUR_DIR}/yolov5"
|
19 |
+
# MODEL_PATH = "runs/train/exp/weights/best.pt"
|
20 |
+
|
21 |
+
def main():
|
22 |
+
st.title("Card number extractor")
|
23 |
+
|
24 |
+
# Use st.camera to capture images from the user's camera
|
25 |
+
img_file_buffer = st.camera_input(label='Please, take a photo of a card')
|
26 |
+
|
27 |
+
# try:
|
28 |
+
# image = Image.open(img_file_buffer)
|
29 |
+
# except:
|
30 |
+
# st.write('No shot detected')
|
31 |
+
|
32 |
+
# Check if an image is captured
|
33 |
+
if img_file_buffer is not None:
|
34 |
+
# Convert the image to a NumPy array
|
35 |
+
image = Image.open(img_file_buffer)
|
36 |
+
image_np = np.array(image)
|
37 |
+
resized_image = cv2.resize(image_np, (128, 128))
|
38 |
+
resized_image = resized_image.astype(np.uint8)
|
39 |
+
resized_image = cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)
|
40 |
+
cv2.imwrite('card_image.jpg', resized_image)
|
41 |
+
|
42 |
+
# original_img = cv2.imread('card_image.jpg')
|
43 |
+
gray = cv2.cvtColor(resized_image, cv2.COLOR_BGR2GRAY)
|
44 |
+
|
45 |
+
x1, y1, x2, y2, card_confidence = get_card_xy(
|
46 |
+
model_path='credit_card_number_detector.tflite',
|
47 |
+
image_path='card_image.jpg'
|
48 |
+
)
|
49 |
+
|
50 |
+
st.write(card_confidence)
|
51 |
+
|
52 |
+
if card_confidence == 0:
|
53 |
+
display_text = "A card is not detected in the image!!!"
|
54 |
+
st.image('card_image.jpg', caption=f"{display_text}", use_column_width=True)
|
55 |
+
else:
|
56 |
+
cropped_image = gray[y1:y2, x1:x2]
|
57 |
+
# cropped_image = resized_image[y1:y2, x1:x2]
|
58 |
+
cropped_image = cv2.resize(cropped_image, (128, 128))
|
59 |
+
cv2.imwrite('card_number_image.jpg', cropped_image)
|
60 |
+
|
61 |
+
extracted_digit = get_digit(
|
62 |
+
model_path="card_number_extractor.tflite",
|
63 |
+
image_path='card_number_image.jpg',
|
64 |
+
threshold=0.4
|
65 |
+
)
|
66 |
+
|
67 |
+
display_text = f'Here is the zoomed card number: {extracted_digit}'
|
68 |
+
st.image('card_number_image.jpg', caption=f"{display_text}", use_column_width=True)
|
69 |
+
|
70 |
+
image = Image.open('card_image.jpg')
|
71 |
+
image_resized = image.resize((640, 640))
|
72 |
+
draw = ImageDraw.Draw(image_resized)
|
73 |
+
draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
|
74 |
+
class_name = 'card'
|
75 |
+
text = f"Class: {class_name}, Confidence: {card_confidence:.2f}"
|
76 |
+
draw.text((x1, y1), text, fill="red")
|
77 |
+
# Saving Images
|
78 |
+
image_resized.save('card_highlighted_image.jpg')
|
79 |
+
display_text = 'Here is the card on the image.'
|
80 |
+
st.image('card_highlighted_image.jpg', caption=f"{display_text}", use_column_width=True)
|
81 |
+
|
82 |
+
st.session_state.pop("card")
|
83 |
+
|
84 |
+
if __name__ == "__main__":
|
85 |
+
main()
|
card_number_extractor.tflite
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:040573544cc95efd2efda024d4d5d526e18b1ec31afd894be877b1ca91627d37
|
3 |
+
size 12102383
|
credit_card_number_detector.tflite
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ce560776ea2d978fb398e2e80add8f8ff25dafecd299c989e873345971024aa0
|
3 |
+
size 12104152
|
extractor.py
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tensorflow as tf
|
2 |
+
import numpy as np
|
3 |
+
from PIL import Image
|
4 |
+
import cv2
|
5 |
+
|
6 |
+
def get_card_xy(model_path, image_path):
|
7 |
+
#model_path = 'odo_detector.tflite'
|
8 |
+
interpreter = tf.lite.Interpreter(model_path=model_path)
|
9 |
+
interpreter.allocate_tensors()
|
10 |
+
|
11 |
+
input_details = interpreter.get_input_details()
|
12 |
+
output_details = interpreter.get_output_details()
|
13 |
+
|
14 |
+
# Obtain the height and width of the corresponding image from the input tensor
|
15 |
+
image_height = input_details[0]['shape'][1] # 640
|
16 |
+
image_width = input_details[0]['shape'][2] # 640
|
17 |
+
|
18 |
+
# Image Preparation
|
19 |
+
# image_name = 'car.jpg'
|
20 |
+
image = Image.open(image_path)
|
21 |
+
image_resized = image.resize((image_width, image_height)) # Resize the image to the corresponding size of the input tensor and store it in a new variable
|
22 |
+
|
23 |
+
image_np = np.array(image_resized) #
|
24 |
+
image_np = np.true_divide(image_np, 255, dtype=np.float32)
|
25 |
+
image_np = image_np[np.newaxis, :]
|
26 |
+
|
27 |
+
# inference
|
28 |
+
interpreter.set_tensor(input_details[0]['index'], image_np)
|
29 |
+
interpreter.invoke()
|
30 |
+
|
31 |
+
# Obtaining output results
|
32 |
+
output = interpreter.get_tensor(output_details[0]['index'])
|
33 |
+
output = output[0]
|
34 |
+
output = output.T
|
35 |
+
|
36 |
+
boxes_xywh = output[:, :4] #Get coordinates of bounding box, first 4 columns of output tensor
|
37 |
+
scores = output[:, 4]#np.max(output[..., 5:], axis=1) #Get score value, 5th column of output tensor
|
38 |
+
classes = np.zeros(len(scores))#np.argmax(output[..., 5:], axis=1) # Get the class value, get the 6th and subsequent columns of the output tensor, and store the largest value in the output tensor.
|
39 |
+
|
40 |
+
# Threshold Setting
|
41 |
+
# threshold = 0.7
|
42 |
+
final_score = 0
|
43 |
+
x_center, y_center, width, height = 0, 0, 0, 0
|
44 |
+
class_name = 'card_number'
|
45 |
+
|
46 |
+
# Bounding boxes, scores, and classes are drawn on the image
|
47 |
+
# draw = ImageDraw.Draw(image_resized)
|
48 |
+
|
49 |
+
for box, score, cls in zip(boxes_xywh, scores, classes):
|
50 |
+
if score >= final_score:
|
51 |
+
x_center, y_center, width, height = box
|
52 |
+
final_score = score
|
53 |
+
class_name = cls
|
54 |
+
else:
|
55 |
+
pass
|
56 |
+
|
57 |
+
x1 = int((x_center - width / 2) * image_width)
|
58 |
+
y1 = int((y_center - height / 2) * image_height)
|
59 |
+
x2 = int((x_center + width / 2) * image_width)
|
60 |
+
y2 = int((y_center + height / 2) * image_height)
|
61 |
+
|
62 |
+
# draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
|
63 |
+
# text = f"Class: {class_name}, Score: {final_score:.2f}"
|
64 |
+
# draw.text((x1, y1), text, fill="red")
|
65 |
+
|
66 |
+
# Saving Images
|
67 |
+
# image_resized.save('test_img.jpg')
|
68 |
+
|
69 |
+
return x1, y1, x2, y2, final_score
|
70 |
+
|
71 |
+
def get_digit(model_path, image_path, threshold=0.5):
|
72 |
+
|
73 |
+
interpreter = tf.lite.Interpreter(model_path=model_path)
|
74 |
+
interpreter.allocate_tensors()
|
75 |
+
|
76 |
+
input_details = interpreter.get_input_details()
|
77 |
+
output_details = interpreter.get_output_details()
|
78 |
+
|
79 |
+
# Obtain the height and width of the corresponding image from the input tensor
|
80 |
+
image_height = input_details[0]['shape'][1] # 640
|
81 |
+
image_width = input_details[0]['shape'][2] # 640
|
82 |
+
|
83 |
+
# Image Preparation
|
84 |
+
# image_name = 'car.jpg'
|
85 |
+
# image = Image.open(image_path2)
|
86 |
+
# image_resized = image.resize((image_width, image_height)) # Resize the image to the corresponding size of the input tensor and store it in a new variable
|
87 |
+
image = cv2.imread(image_path)
|
88 |
+
# image_resized = np.resize(image, (image_width, image_height, 3))
|
89 |
+
|
90 |
+
image_np = np.array(image) #
|
91 |
+
image_np = np.true_divide(image_np, 255, dtype=np.float32)
|
92 |
+
image_np = image_np[np.newaxis, :]
|
93 |
+
|
94 |
+
# inference
|
95 |
+
interpreter.set_tensor(input_details[0]['index'], image_np)
|
96 |
+
interpreter.invoke()
|
97 |
+
|
98 |
+
# Obtaining output results
|
99 |
+
output = interpreter.get_tensor(output_details[0]['index'])
|
100 |
+
output = output[0]
|
101 |
+
output = output.T
|
102 |
+
|
103 |
+
boxes_xywh = output[:, :4] #Get coordinates of bounding box, first 4 columns of output tensor
|
104 |
+
scores = np.max(output[:, 4:], axis=1) #Get score value, 5th column of output tensor
|
105 |
+
classes = np.argmax(output[:, 4:], axis=1) # Get the class value, get the 6th and subsequent columns of the output tensor, and store the largest value in the output tensor.
|
106 |
+
|
107 |
+
pred_list = []
|
108 |
+
|
109 |
+
prob_threshold = threshold
|
110 |
+
|
111 |
+
for box, score, cls in zip(boxes_xywh, scores, classes):
|
112 |
+
|
113 |
+
if score < prob_threshold:
|
114 |
+
continue
|
115 |
+
|
116 |
+
x_center, y_center, width, height = box
|
117 |
+
x1 = int((x_center - width / 2) * image_width)
|
118 |
+
y1 = int((y_center - height / 2) * image_height)
|
119 |
+
x2 = int((x_center + width / 2) * image_width)
|
120 |
+
y2 = int((y_center + height / 2) * image_height)
|
121 |
+
|
122 |
+
pred_list.append((x1, x2, cls, score))
|
123 |
+
|
124 |
+
pred_list = sorted(pred_list, key=lambda x: x[0])
|
125 |
+
|
126 |
+
num_list = []
|
127 |
+
|
128 |
+
temp_pred_list =[]
|
129 |
+
|
130 |
+
x_prev = 0
|
131 |
+
|
132 |
+
x_diff = min([elem[1] - elem[0] for elem in pred_list]) - 10
|
133 |
+
|
134 |
+
for idx, pred in enumerate(pred_list):
|
135 |
+
|
136 |
+
if idx == 0:
|
137 |
+
temp_pred_list.append(pred)
|
138 |
+
x_prev = pred[0]
|
139 |
+
elif idx == len(pred_list) - 1:
|
140 |
+
temp_final_num = sorted(temp_pred_list, key=lambda x: x[-1], reverse=True)[0]
|
141 |
+
num_list.append(temp_final_num)
|
142 |
+
elif pred[0] - x_prev < x_diff:
|
143 |
+
temp_pred_list.append(pred)
|
144 |
+
x_prev = pred[0]
|
145 |
+
else:
|
146 |
+
temp_final_num = sorted(temp_pred_list, key=lambda x: x[-1], reverse=True)[0]
|
147 |
+
num_list.append(temp_final_num)
|
148 |
+
temp_pred_list = []
|
149 |
+
x_prev = pred[0]
|
150 |
+
temp_pred_list.append(pred)
|
151 |
+
|
152 |
+
sorted_number_list = sorted(num_list, key=lambda x: x[0])
|
153 |
+
# sorted_number_list = sorted(sorted_number_list, reverse=True, key= lambda x: x[-1])
|
154 |
+
# output_digit = float(''.join([str(int(i[2])) if i[2]!=10 else '.' for i in sorted_number_list]))
|
155 |
+
output_digit = float(''.join([str(int(i[2])) if i[2]!=10 else '.' for i in sorted_number_list]))
|
156 |
+
# output_digit = ''.join([str(int(i[2])) if i[2]!=10 else '.' for i in sorted_number_list[:10]])
|
157 |
+
|
158 |
+
return output_digit
|
159 |
+
|
requirements.txt.txt
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
absl-py==2.1.0
|
2 |
+
altair==5.2.0
|
3 |
+
astunparse==1.6.3
|
4 |
+
attrs==23.2.0
|
5 |
+
blinker==1.7.0
|
6 |
+
cachetools==5.3.2
|
7 |
+
certifi==2023.11.17
|
8 |
+
charset-normalizer==3.3.2
|
9 |
+
click==8.1.7
|
10 |
+
colorama==0.4.6
|
11 |
+
flatbuffers==23.5.26
|
12 |
+
gast==0.5.4
|
13 |
+
gitdb==4.0.11
|
14 |
+
GitPython==3.1.41
|
15 |
+
google-auth==2.26.2
|
16 |
+
google-auth-oauthlib==1.2.0
|
17 |
+
google-pasta==0.2.0
|
18 |
+
grpcio==1.60.0
|
19 |
+
h5py==3.10.0
|
20 |
+
idna==3.6
|
21 |
+
importlib-metadata==7.0.1
|
22 |
+
Jinja2==3.1.3
|
23 |
+
jsonschema==4.21.1
|
24 |
+
jsonschema-specifications==2023.12.1
|
25 |
+
keras==2.15.0
|
26 |
+
libclang==16.0.6
|
27 |
+
Markdown==3.5.2
|
28 |
+
markdown-it-py==3.0.0
|
29 |
+
MarkupSafe==2.1.4
|
30 |
+
mdurl==0.1.2
|
31 |
+
ml-dtypes==0.2.0
|
32 |
+
numpy==1.26.3
|
33 |
+
oauthlib==3.2.2
|
34 |
+
opencv-python==4.9.0.80
|
35 |
+
opt-einsum==3.3.0
|
36 |
+
packaging==23.2
|
37 |
+
pandas==2.2.0
|
38 |
+
pillow==10.2.0
|
39 |
+
protobuf==4.23.4
|
40 |
+
pyarrow==15.0.0
|
41 |
+
pyasn1==0.5.1
|
42 |
+
pyasn1-modules==0.3.0
|
43 |
+
pydeck==0.8.1b0
|
44 |
+
Pygments==2.17.2
|
45 |
+
python-dateutil==2.8.2
|
46 |
+
pytz==2023.3.post1
|
47 |
+
referencing==0.32.1
|
48 |
+
requests==2.31.0
|
49 |
+
requests-oauthlib==1.3.1
|
50 |
+
rich==13.7.0
|
51 |
+
rpds-py==0.17.1
|
52 |
+
rsa==4.9
|
53 |
+
six==1.16.0
|
54 |
+
smmap==5.0.1
|
55 |
+
streamlit==1.30.0
|
56 |
+
tenacity==8.2.3
|
57 |
+
tensorflow==2.15.0
|
58 |
+
termcolor==2.4.0
|
59 |
+
toml==0.10.2
|
60 |
+
toolz==0.12.0
|
61 |
+
tornado==6.4
|
62 |
+
typing_extensions==4.9.0
|
63 |
+
tzdata==2023.4
|
64 |
+
tzlocal==5.2
|
65 |
+
urllib3==2.1.0
|
66 |
+
validators==0.22.0
|
67 |
+
watchdog==3.0.0
|
68 |
+
Werkzeug==3.0.1
|
69 |
+
wrapt==1.14.1
|
70 |
+
zipp==3.17.0
|