Skip to content

Instantly share code, notes, and snippets.

@giulioz
Created April 16, 2023 10:58
Show Gist options
  • Save giulioz/07f4ea1628518fcfe92cb1b3458e7c53 to your computer and use it in GitHub Desktop.
Save giulioz/07f4ea1628518fcfe92cb1b3458e7c53 to your computer and use it in GitHub Desktop.
OpenCV punch card reader
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