-
Notifications
You must be signed in to change notification settings - Fork 6
/
process.py
executable file
·146 lines (115 loc) · 3.62 KB
/
process.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/python3
# Optical decoding of track 2 of a credit card
#
# https://www.anfractuosity.com/projects/optical-magnetic-stripe-reading/
import argparse
import math
import os
import sys
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
# Average
def avg(l):
return math.ceil(float(sum(l)) / len(l) if len(l) > 0 else float("nan"))
# Process credit card image and return numeric data
def process(jpg):
if not os.path.exists(jpg):
print("Please provide an image that exists",file=sys.stderr)
return
# Read image and threshold it
card = np.array(Image.open(jpg).convert('L'))
card = card > card.mean()
width = card.shape[1]
height = card.shape[0]
pixels = np.zeros((width))
# Create array to represent possible magnetic field lines
for y in range(0, height):
for x in range(0, width):
if card[y][x]:
pixels[x] = pixels[x] + 1
lines = []
# Record the x position of each line
for i in range(len(pixels)):
# if >90% of pixels in column are 'on' likely part of magnetic line
if pixels[i] > ((height / 100) * 90):
lines.append(i)
lastlinepos = -1
width = 0
sep = []
widths = []
# Create a list of the widths, between lines
for linepos in lines:
if not lastlinepos == -1:
widths.append(int(linepos - lastlinepos))
lastlinepos = linepos
widths = sorted(widths)
# Separation between lines need to classify as 0s
sepdist = avg(widths[-int((len(widths)/100)*20):])
# Width in pixels of magnetic field line
maxstripewidth = avg(widths)
old = 0 # last line position
pos = [] # position of lines
yaxis = [] # y axis value for 'dot' for visualisation
# Create list of line positions above a certain width
for lpos in lines:
if lpos > old+maxstripewidth:
pos.append(lpos)
yaxis.append(height/2)
old = lpos
bitstr = ""
marker = 0
# Generate binary data as a string
for i in range(0, len(pos)):
if i > 0:
if pos[i]-pos[i-1] > sepdist:
bitstr = bitstr + "0"
marker = 0
else:
marker = marker + 1
if marker == 2:
bitstr = bitstr + "1"
marker = 0
# Mapping of binary data
bcd = {
"10000": "0",
"00001": "1",
"00010": "2",
"10011": "3",
"00100": "4",
"10101": "5",
"10110": "6",
"00111": "7",
"01000": "8",
"11001": "9",
"11010": ":",
"01011": ";",
"11100": "<",
"01101": "=",
"01110": ">",
"11111": "?",
}
possible = [] # possible card numbers
start = "11111" # card starts with
for x in range(0, len(bitstr)):
if bitstr[x : x + len(start)] == start: # found potential start
out = ""
q = x
while True:
chunk = bitstr[q : q + len(start)]
if len(chunk) == len(start):
if chunk not in bcd:
break
out = bcd[chunk] + out
else:
break
q += len(start)
possible.append(out)
print("Card:", max(possible, key=len)) # print card number with longest length
plt.scatter(pos, yaxis)
plt.imshow(card)
plt.show() # show visualisation
parser = argparse.ArgumentParser()
parser.add_argument("image", help="Filename of track 2 of a credit card image")
args = parser.parse_args()
process(args.image)