random-stuff/python_scripts/image_pipeline.py

119 lines
3.7 KiB
Python
Raw Permalink Normal View History

2023-08-01 12:46:22 +05:00
import cv2, numpy, glob
def read_img(file):
img = cv2.imread(file)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return img
def order_corners(corners):
xSorted = corners[numpy.argsort(corners[:, 0]), :]
left = xSorted[:2, :]
tl, bl = left[numpy.argsort(left[:, 1]), :]
right = xSorted[2:, :]
tr, br = right[numpy.argsort(right[:, 1]), :]
return numpy.array([tl, tr, bl, br], 'float32')
def autocrop(img):
# save copy to apply transform
orig_img = img.copy()
# smoothing to remove text and other small sharp edges
kernel = numpy.ones((5,5), numpy.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=5)
# edge detection + best contours
median_pixel_inten = numpy.median(img)
img = cv2.Canny(img, int(max(0, 0.7*median_pixel_inten)), int(min(255, 1.3*median_pixel_inten)), 3)
img = cv2.dilate(img, None, iterations=1)
img = cv2.erode(img, None, iterations=1)
conts, _ = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
conts = sorted(conts, key=cv2.contourArea, reverse=True)[:10]
# compute estimated bounding corners
for cont in conts:
corners = cv2.approxPolyDP(cont, cv2.arcLength(cont, True) * 0.02, True)
# if 4 corners found and cover an area > 1k pixels
if len(corners) == 4 and cv2.contourArea(corners) >= 1000:
corners = corners.reshape((4,2))
break
# default incase none found
corners = numpy.array([[0,0],[img.shape[1], 0], [0, img.shape[0]], [img.shape[1], img.shape[0]]])
# proper ordering for transformation calc
corners = order_corners(corners)
# compute new dimensions
w1 = numpy.sqrt((corners[3][0] - corners[2][0]) ** 2 + (corners[3][1] - corners[3][1]) ** 2)
w2 = numpy.sqrt((corners[1][0] - corners[0][0]) ** 2 + (corners[1][1] - corners[0][1]) ** 2)
h1 = numpy.sqrt((corners[3][0] - corners[1][0]) ** 2 + (corners[3][1] - corners[1][1]) ** 2)
h2 = numpy.sqrt((corners[2][0] - corners[0][0]) ** 2 + (corners[2][1] - corners[0][1]) ** 2)
max_w = int(max(w1, w2))
max_h = int(max(h1, h2))
dest_corners = numpy.array([[0, 0], [max_w - 1, 0], [0, max_h - 1] ,[max_w - 1, max_h - 1]], 'float32')
# compute and apply transformation
transf_mat = cv2.getPerspectiveTransform(corners, dest_corners)
img = cv2.warpPerspective(orig_img, transf_mat, (max_w-1, max_h-1), flags = cv2.INTER_LINEAR)
# rescale for next steps
img = cv2.resize(img, (int(img.shape[1] * 1200 / img.shape[0]), 1200))
return img
def is_low_contrast(img, thresh):
# reduce noise
pixel_range = numpy.percentile(img, [5,95])
# calc spread mesure
val = (pixel_range[1] - pixel_range[0]) / 255
return val < thresh
def is_blurry(img, thresh):
# compute laplacian
lap = cv2.Laplacian(img, cv2.CV_64F)
# remove noise and find largest value
blur = numpy.percentile(lap, [0, 99])[1]
return blur < thresh
def remove_shadows(img):
# spread out pixels
dil = cv2.dilate(img, (7,7))
# blur to infer background
blur = cv2.medianBlur(dil, 25)
# remove smoothed background (shadows)
img = 225 - cv2.absdiff(img, blur)
# threshold for b&w
_, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
return img
def write_img(img, file):
cv2.imwrite('outs/' + file, img)
def process_img(img):
if is_low_contrast(img, 0.6):
return -1, img
img = autocrop(img)
if is_blurry(img, 70):
return -1, img
img = remove_shadows(img)
return 0, img
def main():
files = glob.glob('*.jpg')
for file in files:
img = read_img(file)
ret, img = process_img(img)
if ret != -1:
write_img(img, file)
if __name__ == '__main__':
main()