Created
April 16, 2023 10:58
-
-
Save giulioz/07f4ea1628518fcfe92cb1b3458e7c53 to your computer and use it in GitHub Desktop.
OpenCV punch card reader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cv2 as cv | |
import numpy as np | |
img = cv.imread('IMG_2024.jpeg') | |
# Segmentation | |
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) | |
h = hsv[:,:,0] | |
s = hsv[:,:,1] | |
v = hsv[:,:,2] | |
th_h = cv.threshold(h,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)[1] | |
th_s = cv.threshold(s,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)[1] | |
th_v = cv.threshold(v,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)[1] | |
segmented = th_h & th_s & th_v | |
# Find features | |
contours, hierarchy = cv.findContours(segmented, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) | |
# Find enclosing rect | |
biggest_rect = contours[0] | |
biggest_rect_area = 0 | |
for contour in contours: | |
area = cv.contourArea(contour) | |
if area > biggest_rect_area: | |
biggest_rect_area = area | |
biggest_rect = contour | |
rect_hull = cv.convexHull(biggest_rect) | |
leftmost = tuple(rect_hull[rect_hull[:,:,0].argmin()][0]) | |
rightmost = tuple(rect_hull[rect_hull[:,:,0].argmax()][0]) | |
topmost = tuple(rect_hull[rect_hull[:,:,1].argmin()][0]) | |
bottommost = tuple(rect_hull[rect_hull[:,:,1].argmax()][0]) | |
approx = np.array([leftmost, topmost, rightmost, bottommost]) | |
rect = cv.minAreaRect(approx) | |
lenA,lenB = rect[1] | |
width = max(lenA,lenB) | |
height = min(lenA,lenB) | |
ratio = width / height | |
# Crop with enclosing rect | |
warp_height = 128 | |
warp_width = int(warp_height * ratio) | |
src = approx.astype(np.float32) | |
dst = np.array([ | |
[0, 0], | |
[warp_width - 1, 0], | |
[warp_width - 1, warp_height - 1], | |
[0, warp_height - 1]], dtype = "float32") | |
M = cv.getPerspectiveTransform(src, dst) | |
cropped = cv.warpPerspective(segmented, M, (warp_width, warp_height)) | |
cropped_color = cv.warpPerspective(img, M, (warp_width, warp_height)) | |
contours_cropped, hierarchy_cropped = cv.findContours(cropped, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) | |
# Find circles | |
calib_circles = [] | |
for contour in contours_cropped: | |
area = cv.contourArea(contour) | |
perimeter = cv.arcLength(contour,True) | |
if perimeter == 0: | |
perimeter = 999999999 | |
cp_ratio = area / perimeter | |
if cp_ratio < 5 and area < 80 and area > 20: | |
calib_circles.append(contour) | |
calib_centers = np.zeros((len(calib_circles),2), dtype=int) | |
for i, circle in enumerate(calib_circles): | |
M = cv.moments(circle) | |
calib_centers[i,0] = int(M['m10']/M['m00']) | |
calib_centers[i,1] = int(M['m01']/M['m00']) | |
sorted_centers = np.sort(calib_centers, 0) | |
sorted_centers_x = sorted_centers[:,0] | |
first_circle_x = sorted_centers_x[0] | |
circle_x_distances = sorted_centers_x - np.roll(sorted_centers_x, 1) | |
median_distance_x = int(np.median(circle_x_distances)) | |
# Reading configuration | |
read_ys = [12,32,72,90,109] | |
count = warp_width // median_distance_x | |
readt_data_bin = np.zeros((count,5), dtype=bool) | |
r = 5 | |
roi = cropped[100 - r: 100 + r, 100 - r: 100 + r] | |
mask = np.zeros_like(roi) | |
cv.circle(mask, (5,5), r, 255, -1) | |
threshold_value = 255/2 | |
# Read the bits from the image | |
for line, y in enumerate(read_ys): | |
x = first_circle_x | |
for column, i in enumerate(range(count)): | |
roi = cropped[y - r: y + r, x - r: x + r] | |
dst = cv.bitwise_and(roi, mask) | |
avg = np.mean(dst) | |
if avg < threshold_value: | |
readt_data_bin[column, line] = True | |
cv.circle(cropped_color, (x, y), r, (0, 0, 255)) | |
else: | |
cv.circle(cropped_color, (x, y), r, (255, 0, 0)) | |
x += median_distance_x | |
# Convert the bits to actual integers | |
readt_data_int = [] | |
for i in range(count): | |
bit_0 = int(readt_data_bin[i,4]) << 0 | |
bit_1 = int(readt_data_bin[i,3]) << 1 | |
bit_2 = int(readt_data_bin[i,2]) << 2 | |
bit_3 = int(readt_data_bin[i,1]) << 3 | |
bit_4 = int(readt_data_bin[i,0]) << 4 | |
value = bit_0 + bit_1 + bit_2 + bit_3 + bit_4 | |
readt_data_int.append(value) | |
print(readt_data_int) | |
lut = [ | |
"Blank", "T", "CR", "O", "Space", "H", "N", "M", "Line Feed", "L", "R", "G", "I", "P", | |
"C", "V", "E", "Z", "D", "B", "S", "Y", "F", "X", "A", "W", "J", "Figure Shift", "U", | |
"Q", "K", "Letter Shift", | |
] | |
readt_data_parsed = [] | |
for i in range(count): | |
char = lut[readt_data_int[i]] | |
if char == "Space": | |
print(" ", end="") | |
elif char == "CR": | |
print("\n", end="") | |
elif len(char) > 1: | |
print(char) | |
else: | |
print(char, end="") | |
cv.imshow("Display window", cropped_color) | |
cv.waitKey(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment