receipt_indexer/code/autocropper/myfunctions.py
Ethan Wellenreiter 7fe6adad99 Quick amendment for module calling
Accidentally uses mf. inside the file for module usage

Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
2023-12-05 18:32:17 -05:00

1178 lines
42 KiB
Python

import cv2
import numpy as np
import math
from deskew import determine_skew
import heapq as hq
import torchvision.transforms.v2 as v2
import scipy.stats as st
## ------------------------------helper functions------------------------------
def ResizeWithAspectRatio(image, width=None, height=None, inter=cv2.INTER_AREA, retscale=False):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
if (retscale == True):
return (image, 1)
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
if (retscale == True):
# print("hi")
return (cv2.resize(image, dim, interpolation=inter), 1/r)
return cv2.resize(image, dim, interpolation=inter)
def squareandthenresize(image, fill=0, width=None, height=None, inter=cv2.INTER_AREA, returnscalerinfo=False):
out = squarepad(image, fill=fill, returnoffset=returnscalerinfo)
if (returnscalerinfo):
squaredimage, hp, vp = out
else:
squaredimage = out
out = ResizeWithAspectRatio(squaredimage, width=width, height=height, inter=inter, retscale=returnscalerinfo)
if (returnscalerinfo):
finalimage, scaler = out
return finalimage, scaler, hp, vp
else:
finalimage = out
return finalimage
# class SquarePad:
# def __init__(self, fill):
# self.fill = fill
# def __call__(self, image):
# w, h = image.shape[1], image.shape[0]
# max_wh = np.max([w, h])
# hp = int((max_wh - w) / 2)
# vp = int((max_wh - h) / 2)
# padding = (hp, vp, hp, vp)
# return cv2.copyMakeBorder(image, vp, vp, hp, hp, cv2.BORDER_CONSTANT, self.fill)
def squarepad(image, fill=0, returnoffset=False):
w, h = image.shape[1], image.shape[0]
max_wh = np.max([w, h])
hp = int((max_wh - w) / 2)
vp = int((max_wh - h) / 2)
padding = (hp, vp, hp, vp)
if (returnoffset):
return cv2.copyMakeBorder(image, vp, vp, hp, hp, cv2.BORDER_CONSTANT, fill), hp, vp
return cv2.copyMakeBorder(image, vp, vp, hp, hp, cv2.BORDER_CONSTANT, fill)
def rotate(img, angle, fill=(0,0,0)):
rows,cols = img.shape[0], img.shape[1]
M = cv2.getRotationMatrix2D((cols/2,rows/2),angle,1)
dst = cv2.warpAffine(img,M,(cols,rows), borderValue=fill)
return dst
def clip(n, lower, upper):
return max(lower, min(n, upper))
def colourscaler(n, min, max):
temp = n-min
diff = abs(max - min)
return clip((temp/diff)*255, 0, 255)
def padWithColour(img, hpadding=0, vpadding=0, fill=(0,0,0)):
borderType = cv2.BORDER_CONSTANT
out = cv2.copyMakeBorder(img, vpadding, vpadding, hpadding, hpadding, borderType, None, fill)
return out
def mergecontours(contours):
cont = np.vstack(contours)
finalcontour = cv2.convexHull(cont)
return finalcontour
# funtion to correct the median-angle to give it to the cv2.warpaffine() function
# specifically, when getting the angle from a minAreaRect rectangle
def anglecorrector(angle):
if 0 <= angle <= 90:
corrected_angle = angle - 90
elif -45 <= angle < 0:
corrected_angle = angle - 90
elif -90 <= angle < -45:
corrected_angle = 90 + angle
return corrected_angle
tensorize = v2.Compose([v2.ToImageTensor(), v2.ConvertImageDtype()]) ## for converting an image (usually PIL image) to a pytorch tensor
## ------------------------------for selective segmentation search crop------------------------------
def rectArea(rect):
# print(rect)
return rect[2]*rect[3]
def biggestRects(n, rects):
dict = {}
# outrects = np.zeros(shape=(n, 4))
for rect in rects:
dict[tuple(rect)] = rectArea(rect)
# maxh.heappush(rectArea(rect))
# print(maxh[0])
heap = [(-value, key) for key,value in dict.items()]
largest = hq.nsmallest(n, heap)
# hq.heapify(list(dict.items()))
# for i in range(0,n):
# outrects[i] = maxh.heappop()
# print(outrects)
return [key for value, key in largest]
def overlapRect(rects):
leftwall = -1
rightwall = -1
topwall = -1
bottomwall = -1
for (x, y, w, h) in rects:
if (leftwall == -1):
leftwall = x
rightwall = x + w
topwall = y
bottomwall = y + h
continue
leftwall = max(leftwall, x)
rightwall = min(rightwall, x+w)
topwall = max(topwall, y)
bottomwall = min(bottomwall, y+h)
if (topwall >= bottomwall or leftwall >= rightwall):
return (-1, -1, -1, -1)
return (leftwall, topwall, rightwall-leftwall, bottomwall-topwall)
def selectiveSearchSegmentationImp(image):
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(image)
ss.switchToSelectiveSearchFast()
return ss.process()
## ------------------------------other rectangle stuff------------------------------
def containsrect(outer, inner, xywhtype=True):
if xywhtype and (outer[0] > inner[0]) or (outer[1] > inner[1]) or (outer[0]+outer[2] < inner[0]+inner[2]) or (outer[1]+outer[3] < inner[1]+inner[3]):
return False
if not xywhtype and (outer[0] > inner[0]) or (outer[1] > inner[1]) or (outer[2] < inner[2]) or (outer[3] < inner[3]):
return False
return True
def xywhrectto2prect(rect):
return (rect[0], rect[1], rect[0]+rect[2], rect[1]+rect[3])
def twoprecttoxywhrect(rect):
return (rect[0], rect[1], rect[2]-rect[0], rect[3]-rect[1])
def mergerects(rects, xywhtype=True):
maxrect = [-1,-1,-1,-1]
for i, rect in enumerate(rects):
if (i == 0):
maxrect[0] = rect[0]
maxrect[1] = rect[1]
maxrect[0] = min(maxrect[0], rect[0])
maxrect[1] = min(maxrect[1], rect[1])
if (xywhtype):
maxrect[2] = max(maxrect[2], rect[0]+rect[2])
maxrect[3] = max(maxrect[3], rect[1]+rect[3])
else:
maxrect[2] = max(maxrect[2], rect[2])
maxrect[3] = max(maxrect[3], rect[3])
if (xywhtype):
maxrect[2] = maxrect[2]-maxrect[0]
maxrect[3] = maxrect[3]-maxrect[1]
return maxrect
def rectscontaining(rect, outerrects):
temprects = set()
for i, outerrect in enumerate(outerrects):
if containsrect(outerrect, rect):
temprects.add(i)
return temprects
## ------------------------------specific to houghline cropping and deskewing------------------------------
def lineAngle(line):
# print(line)
angle = (math.atan2(line[3] - line[1], line[2] - line[0]) % np.pi) - (np.pi/2)
return angle
def WithinXDegrees(lines, margin, baseangle=0):
# outlines = np.array([[]])
outlines = np.empty((0, 4))
# print(outlines.shape)
for line in lines:
# print(type(line))
# print(abs(lineAngle(line[0])))
if (np.rad2deg(abs(lineAngle(line[0])+np.deg2rad(baseangle))) <= margin):
outlines = np.append(outlines, [line[0]], axis=0)
return outlines
def lineBoundingRect(lines, asRect=False, returnint=False):
maxvals = lines.max(0)
minvals = lines.min(0)
x1 = min(minvals[0],minvals[2])
y1 = min(minvals[1],minvals[3])
x2 = max(maxvals[0],maxvals[2])
y2 = max(maxvals[1],maxvals[3])
if (asRect):
x2 -= x1
y2 -= y1
if (returnint):
x1 = int(x1)
y1 = int(y1)
x2 = int(x2)
y2 = int(y2)
x1 = max(0, x1)
x2 = max(0,x2)
y1 = max(0, y1)
y2 = max(0, y2)
return (x1,y1,x2,y2)
# print(lines.max(0))
# print(type(lines))
def lineswithinrange(lines, pt1, pt2, x=True, y=False):
out_lines = lines
if (x):
minx = min(pt1[0], pt2[0])
maxx = max(pt1[0], pt2[0])
out_lines = [line for line in out_lines if ((min(line[0],line[2]) >= minx) and (max(line[0],line[2]) <= maxx))]
if (y):
miny = min(pt1[1], pt2[1])
maxy = max(pt1[1], pt2[1])
out_lines = [line for line in out_lines if ((min(line[1],line[3]) >= minx) and (max(line[1],line[3]) <= maxx))]
return out_lines
def premorphCrop(image):
# convert to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
window = gray.shape[1]//8
if window % 2 == 0:
window += 1
# print(window)
# gray = cv2.blur(gray, (11,11))
# threshold
# thresh = cv2.threshold(gray, 170, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, window, 2)
# return thresh
# apply morphology
kernel = np.ones((9,9), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_ERODE, kernel)
# morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((9,9), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((3,3), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# return morph
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = 0
for c in contours:
area = cv2.contourArea(c)
if area > area_thresh:
area_thresh = area
big_contour = c
# get bounding box
x,y,w,h = cv2.boundingRect(big_contour)
# draw filled contour on black background
mask = np.zeros_like(gray)
mask = cv2.merge([mask,mask,mask])
# mask = cv2.blur(mask,(121,121))
cv2.drawContours(mask, [big_contour], -1, (255,255,255), cv2.FILLED)
# apply mask to input
result1 = image.copy()
mask = cv2.blur(mask,(3,3))
result1 = cv2.bitwise_and(result1, mask)
# crop result
result2 = result1[y:y+h, x:x+w]
return result2, (x,y,w,h)
def rotatePoint(img, pt, angle, returnint=True):
rotateaxisx = img.shape[0]/2
rotateaxisy = img.shape[1]/2
tempx = pt[0] - rotateaxisx
tempy = pt[1] - rotateaxisy
rotatedx = tempx*math.cos(np.deg2rad(-angle)) - tempy*math.sin(np.deg2rad(-angle))
rotatedy = tempx*math.sin(np.deg2rad(-angle)) + tempy*math.cos(np.deg2rad(-angle))
finalx = rotatedx + rotateaxisx
finaly = rotatedy + rotateaxisy
if (returnint):
finalx = int(finalx)
finaly = int(finaly)
return (finalx, finaly)
def rotateRect(img, rect, angle, returnint=True, asRect=False):
if (asRect):
pt1 = rotatePoint(img, (rect[0],rect[1]), angle, returnint)
pt2 = rotatePoint(img, (rect[0]+rect[2],rect[1]+rect[3]), angle, returnint)
return (pt1[0], pt1[1], pt2[0]-pt1[0], pt2[1]-pt1[1])
else:
pt1 = rotatePoint(img, (rect[0],rect[1]), angle, returnint)
pt2 = rotatePoint(img, (rect[2],rect[3]), angle, returnint)
return (pt1[0], pt1[1], pt2[0], pt2[1])
def rotateLine(img, line, angle, returnint=True):
pt1 = rotatePoint(img, (line[0],line[1]), angle, returnint)
pt2 = rotatePoint(img, (line[2],line[3]), angle, returnint)
return (pt1[0], pt1[1], pt2[0], pt2[1])
def prepimageforhoughline(image, returnrect=True):
prepped, scaler, hp, vp = squareandthenresize(image, fill=255, width=1000, returnscalerinfo=True)
ogpreppedshape = prepped.shape
prepped, croprect = premorphCrop(prepped)
if (prepped.shape[1] > prepped.shape[0]):
prepped, preppedscaler = ResizeWithAspectRatio(prepped, width=1000, retscale=True)
else:
prepped, preppedscaler = ResizeWithAspectRatio(prepped, height=1000, retscale=True)
finalcroprect = (int(croprect[0]*scaler - hp), int(croprect[1]*scaler - vp), int(croprect[2]*scaler), int(croprect[3]*scaler))
gray1 = cv2.cvtColor(prepped, cv2.COLOR_BGR2GRAY)
dst1 = cv2.Canny(gray1, 0, 500, None, 3)
kernel = np.ones((5,5), np.uint8)
out = cv2.morphologyEx(dst1, cv2.MORPH_DILATE, kernel)
out = cv2.blur(out, (5,5))
kernel = np.ones((6,6), np.uint8)
dst1 = cv2.morphologyEx(out, cv2.MORPH_ERODE, kernel)
# return dst1
dst1 = cv2.Canny(dst1, 0, 500, None, 3)
# return dst1
accompaniedimage = image[finalcroprect[1]:finalcroprect[1]+finalcroprect[3], finalcroprect[0]:finalcroprect[0]+finalcroprect[2], :]
if returnrect:
borderType = cv2.BORDER_CONSTANT
preppadding = [croprect[0], croprect[1], ogpreppedshape[1]-(croprect[0]+croprect[2]), ogpreppedshape[0]-(croprect[1]+croprect[3])]
preppadding = [int(s/preppedscaler) for s in preppadding]
paddedprepped = cv2.copyMakeBorder(dst1, preppadding[1], preppadding[3], preppadding[0], preppadding[2], borderType, 0)
squaredimage = squarepad(image, fill=0)
return dst1, accompaniedimage, paddedprepped, squaredimage, finalcroprect
else:
return dst1, accompaniedimage
def houghlinedeskewangle(image):
lines = cv2.HoughLines(image, 1, np.pi/180, int(max(image.shape[0], image.shape[1])/6), None, 0, 0)
angles = np.zeros(len(lines))
if lines is not None:
for i in range(0, len(lines)):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
unroundedpt1 = (x0 + 1000*(-b), y0 + 1000*(a))
unroundedpt2 = (x0 - 1000*(-b), y0 - 1000*(a))
pt1 = (int(unroundedpt1[0]), int(unroundedpt1[1]))
pt2 = (int(unroundedpt2[0]), int(unroundedpt2[1]))
v1_theta = math.atan2(pt1[1], pt1[0])
v2_theta = math.atan2(pt2[1], pt2[0])
# print(math.atan2(unroundedpt2[1] - unroundedpt1[1], unroundedpt2[0] - unroundedpt1[0]) % np.pi)
# print(lineAngle((unroundedpt1[0], unroundedpt1[1], unroundedpt2[0], unroundedpt2[1])))
# angles[i] = math.atan2(unroundedpt2[1] - unroundedpt1[1], unroundedpt2[0] - unroundedpt1[0]) % np.pi
angles[i] = lineAngle((unroundedpt1[0], unroundedpt1[1], unroundedpt2[0], unroundedpt2[1]))
# cv2.line(cdstP, pt1, pt2, (0,0,255), 3, cv2.LINE_AA)
mode = st.mode(np.around(angles, decimals=3))[0]
rotationangle = np.rad2deg(mode)
return rotationangle
def determineextrapadding(h,w, angle):
radangle = abs(np.deg2rad(angle))
# print(type(h), type(w), type(angle))
# print(h, w, angle)
# print(radangle)
totalheightrot = w*np.sin(radangle) + h*np.cos(radangle)
# print(h, totalheightrot)
totalwidthrot = h*np.sin(radangle) + w*np.cos(radangle)
# print(w, totalwidthrot)
vpad = int(max(0,math.ceil((totalheightrot - h)/2)))
hpad = int(max(0,math.ceil((totalwidthrot-w)/2)))
# print(vpad, hpad)
return hpad, vpad
def rotatewithexactpadding(img, angle, fill=(0,0,0)):
h, w = img.shape[0], img.shape[1]
hpad, vpad = determineextrapadding(h=h,w=w, angle=angle)
# fill1 = fill
# print(fill)
baseimage = padWithColour(img, hpad, vpad, fill=fill)
# return baseimage
rotatedimg = rotate(baseimage, angle,fill=fill)
return rotatedimg
def houghlinepcrop(baseimage, preppedimage, scalingmultiplier):
rotatedlines = cv2.HoughLinesP(preppedimage, 1, np.pi / 180, 30, None, 90, 30)
vmarginlines = WithinXDegrees(rotatedlines, 7)
hmarginlines = WithinXDegrees(rotatedlines, 7, baseangle=90)
# vrect = lineBoundingRect(vmarginlines,asRect=False, returnint=True)
# hmarginlines = lineswithinrange(hmarginlines, (vrect[0], vrect[1]), (vrect[2],vrect[3]), x=True, y=False)
marginlines = np.append(vmarginlines, hmarginlines, axis=0)
# colourdst = cv2.cvtColor(preppedimage, cv2.COLOR_GRAY2BGR)
# if marginlines is not None:
# for l in marginlines:
# cv2.line(colourdst, (int(l[0]), int(l[1])), (int(l[2]), int(l[3])), (0,0,255), 3, cv2.LINE_AA)
# return colourdst
rect = lineBoundingRect(marginlines,asRect=False, returnint=True)
scaledrect = (int(rect[0]*scalingmultiplier), int(rect[1]*scalingmultiplier), int(rect[2]*scalingmultiplier), int(rect[3]*scalingmultiplier))
croppedbaseimage = baseimage[scaledrect[1]:scaledrect[3], scaledrect[0]:scaledrect[2], :]
return croppedbaseimage
def contourcrop(baseimage):
shrunkencbi, sizemultiplier = ResizeWithAspectRatio(baseimage, width=1000, retscale=True)
gray = cv2.cvtColor(shrunkencbi, cv2.COLOR_BGR2GRAY)
# thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_TRIANGLE)[1]
# window = gray.shape[1]//7
# if window % 2 == 0:
# window += 1
# thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, window, 10)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
# thresh = cv2.morphologyEx(thresh, cv2.MORPH_ERODE, kernel, iterations=2)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# return thresh
contours, heirarchy = cv2.findContours(thresh,cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# temp = cv2.drawContours(shrunkencbi, contours, -1, (0,255,0), thickness=3)
# biggestcontour = max(contours, key=cv2.contourArea)
# temp = cv2.drawContours(shrunkencbi, [biggestcontour], -1, (0,255,0), thickness=3)
# return temp
mx = (0,0,0,0)
mx_area = 0
for i, cont in enumerate(contours):
rect = cv2.boundingRect(cont)
area = rectArea(rect)
if (area > mx_area):
mx = rect
mx_area = area
scaledmx = (int(mx[0]*sizemultiplier), int(mx[1]*sizemultiplier), int(mx[2]*sizemultiplier), int(mx[3]*sizemultiplier))
finalbaseimage = baseimage[scaledmx[1]:scaledmx[1]+scaledmx[3], scaledmx[0]:scaledmx[0]+scaledmx[2], :]
return finalbaseimage
def houghlinedeskewthencrop(baseimage, preppedimage, rotationangle, croprect):
rotatedbaseimage = rotatewithexactpadding(baseimage, rotationangle, fill=(0,0,0))
rotateddst1 = rotatewithexactpadding(preppedimage, rotationangle, fill=(0,0,0))
sizemultiplier = rotatedbaseimage.shape[0]/rotateddst1.shape[0]
croppedbaseimage = houghlinepcrop(rotatedbaseimage, rotateddst1, sizemultiplier)
finalbaseimage = contourcrop(croppedbaseimage)
return finalbaseimage, rotationangle
def houghlinedeskewandcrop(image):
croppedcanny, croppedimage, canny, ogimage, rect = prepimageforhoughline(image, returnrect=True) ## scaling and cropping occurs. need to also return the changes done
# return canny, ogimage
# print(canny.shape)
# print(croppedogimage.shape)
## -----------------finding angle to deskew-----------------
rotationangle = houghlinedeskewangle(croppedcanny)
# print(croppedcanny.shape)
# print(abs(rotationangle))
if (croppedcanny.shape[0] > croppedcanny.shape[1]):
if (rotationangle > 45):
rotationangle -= 90
elif rotationangle < -45:
rotationangle += 90
# print(rotationangle)
# elif (croppedcanny.shape[1] > croppedcanny.shape[0]):
# if (rotationangle > 45):
# rotationangle -= 90
# elif rotationangle < -45:
# rotationangle += 90
# print(rotationangle)
# rotatorrect = findcroprectforangle(rect, angle)
# -----------------end of finding angle to deskew-----------------
## -----------------deskewing and then cropping-----------------
outimg, angle = houghlinedeskewthencrop(ogimage, canny, rotationangle, rect)
return outimg, angle
def bruteforceprocessrects(greaterrects, lesserrects):
# squaredgrects = np.array([mf.xywhrectto2prect(rect) for rect in greaterrects])
# squaredlrects = np.array([mf.xywhrectto2prect(rect) for rect in lesserrects])
# print(squaredgrects)
# print(type(squaredgrects))
# greatersortedbylowerx = (greaterrects[:,0]).argsort()
# greatersortedbylowery = (greaterrects[:,1]).argsort()
# greatersortedbyupperx = (greaterrects[:,0]+greaterrects[:,2]).argsort()
# greatersortedbyuppery = (greaterrects[:,1]+greaterrects[:,3]).argsort()
outerboxes = []
for innerrect in lesserrects:
outerboxes.append(rectscontaining(innerrect, greaterrects))
actingrects = lesserrects.copy()
##IMPLEMENT BRUTEFORCE MERGE/RECHECKCONTAINS HERE
i = 0
while (i < len(actingrects)):
for j in range(i+1, len(outerboxes)):
# print("i ", i, " j ", j)
if (len(outerboxes[i].intersection(outerboxes[j])) != 0):
mergedrect = mergerects([actingrects[i], actingrects[j]])
# print(actingrects[i], actingrects[j], mergedrect)
actingrects[i] = mergedrect
# print(actingrects)
actingrects = np.delete(actingrects, j, axis=0)
# print(actingrects)
outerboxes[i] = rectscontaining(actingrects[i], greaterrects)
outerboxes.pop(j)
i = i-1
break
i = i+1
# print(actingrects)
return actingrects
def processrects(greaterrects, lesserrects):
return bruteforceprocessrects(greaterrects, lesserrects)
def whiteoutbackground(image):
imagecpy = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# blur = cv2.blur(gray, (7,7))
# window = 51
window = gray.shape[1]//8
if window % 2 == 0:
window += 1
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, window, 2)
# thresh2 = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)[1]
# thresh = cv2.bitwise_and(thresh1, thresh2)
# return thresh
# dim = int(min(thresh.shape[0], thresh.shape[1])/400)
# dim = 3
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (dim, dim))
# morphedthresh = cv2.morphologyEx(thresh, cv2.MORPH_ERODE, kernel)
# return morphedthresh
contours1, heirarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# contours2, heirarchy = cv2.findContours(morphedthresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
biggestcontour1 = max(contours1, key=cv2.contourArea)
# biggestcontour2 = max(contours2, key=cv2.contourArea)
epsilon = 0.0005*cv2.arcLength(biggestcontour1,True)
approx = cv2.approxPolyDP(biggestcontour1,epsilon,True)
# approx = cv2.convexHull(approx)
epsilon = 0.001*cv2.arcLength(approx,True)
approx = cv2.approxPolyDP(approx,epsilon,True)
# approx = cv2.convexHull(biggestcontour1)
# print(approx)
# imagecpy = cv2.drawContours(imagecpy, [biggestcontour1], -1, (0,255,0), thickness=3)
# imagecpy = cv2.drawContours(imagecpy, [biggestcontour2], -1, (0,0,255), thickness=3)
# imagecpy = cv2.drawContours(imagecpy, [approx], -1, (0,255,0), thickness=3)
# return imagecpy
blank = np.full(thresh.shape, 255, dtype=np.uint8)
mask = blank.copy()
mask = cv2.drawContours(mask, [biggestcontour1], -1, (0,0,0), thickness=cv2.FILLED)
# mask = cv2.drawContours(mask, [approx], -1, (0,0,0), thickness=cv2.FILLED)
# mask = cv2.drawContours(mask, [biggestcontour2], -1, (0,0,0), thickness=cv2.FILLED)
# return mask
invertmask = 255 - mask
dim = int(min(invertmask.shape[0], invertmask.shape[1])/200)
# # dim = 21
# print(dim)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (dim, dim))
# invertmask = cv2.morphologyEx(invertmask, cv2.MORPH_DILATE, kernel)
mask = 255 - cv2.morphologyEx(invertmask, cv2.MORPH_ERODE, kernel, iterations=1)
# return mask
mask1 = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
whitedbackground = cv2.bitwise_or(image, mask1)
# return whitedbackground
mask2 = blank.copy()
mask2 = 255-cv2.drawContours(mask2, [approx], -1, (0,0,0), thickness=cv2.FILLED)
dim = int(min(mask2.shape[0], mask2.shape[1])/50)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (dim, dim))
morphedmask = 255-cv2.morphologyEx(mask2, cv2.MORPH_OPEN, kernel, iterations=3)
# return morphedmask
finalmask = cv2.bitwise_or(mask, morphedmask)
finalmaskbgr = cv2.cvtColor(finalmask, cv2.COLOR_GRAY2BGR)
# return finalmaskbgr
whitedbackground = cv2.bitwise_or(whitedbackground, finalmaskbgr)
# return whitedbackground
test = cv2.inpaint(whitedbackground, finalmask, 3, cv2.INPAINT_TELEA)
return test
def removeCardinalLines(image, horizontal=False):
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
axis = 0
if (horizontal):
cardinal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))
axis = 1
else:
cardinal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,15))
lines = cv2.morphologyEx(image, cv2.MORPH_OPEN, cardinal_kernel, iterations=2)
# lines = cv2.morphologyEx(lines, cv2.MORPH_OPEN, kernel, iterations=2)
# return lines
mask = np.zeros(image.shape, dtype=np.uint8)
contours, _ = cv2.findContours(255-lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# mask = cv2.drawContours(mask, contours, -1, 255, thickness=3)
# return mask
boxes = []
dims = np.array([])
rects = []
for contour in contours:
rect = cv2.minAreaRect(contour)
rect = list(rect)
rect[1]=list(rect[1])
if (rect[1][axis] > rect[1][1-axis]):
rect[2] = rect[2] -90
temp = rect[1][1]
rect[1][1]=rect[1][0]
rect[1][0]=temp
# print(rect)
rects.append(rect)
dims = np.append(dims, rect[1][axis])
# box = cv2.boxPoints(rect)
# box = np.intp(box)
# boxes.append(box)
# mask = cv2.drawContours(mask, [box], -1, 255, thickness=2)
# break
# return mask
# print(dims)
meddim = np.median(dims)
# print(meddim)
for rect in rects:
# print(rect[1][axis])
# print(meddim/2)
# print(rect[1][1-axis])
# print(rect[1][axis])
if (rect[1][axis] < meddim/2 and rect[1][1-axis] > image.shape[axis]/5):
adjustedrect = rect
adjustedrect[1][0] += 3
adjustedrect[1][1] += 3
box = cv2.boxPoints(adjustedrect)
box = np.intp(box)
# boxes.append(box)
# mask = cv2.drawContours(mask, [box], -1, 255, thickness=2)
image = cv2.drawContours(image, [box], -1, 255, thickness=cv2.FILLED)
# return mask
return image
def removeLinesFromText(image):
image = removeCardinalLines(image)
image = removeCardinalLines(image, horizontal=True)
return image
def cropclarifying(image):
whitedbackground = whiteoutbackground(image)
# return whitedbackground
textrefined = textClarifying(whitedbackground)
# return textrefined
#maybe now is when I put in the line removing function
lineout = removeLinesFromText(textrefined)
return lineout
# implement a function that's called refine text
def croptoblack(image, extraborder=10, returnrect=False):
invertedimage = cv2.bitwise_not(image)
blackpixels = cv2.findNonZero(invertedimage)
mins = np.min(blackpixels, axis=0)
minx = max(mins[0][0]-extraborder, 0)
miny = max(mins[0][1]-extraborder, 0)
maxs = np.max(blackpixels, axis=0)
maxx = min(maxs[0][0]+extraborder, image.shape[1])
maxy = min(maxs[0][1]+extraborder, image.shape[0])
# print(blackpixels)
if (returnrect):
return [minx,miny,maxx-minx,maxy-miny]
return image[miny:maxy, minx:maxx]
def reduceColours(x, centering=127):
a=0.00008
b=40
c=256
x = x.astype(int)
# value = np.cbrt((x-centering)/a)+centering
value = -((c+4)/(1+np.exp((x-centering)/b)))+c
value = np.clip(value, 0, 255)
return value.astype(np.uint8)
def bwadjustment(image, center=127):
gray = reduceColours(image,center)
return gray
def textClarifying(image):
## Try using the LAB colour space???
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
autothreshold = np.clip(np.mean(gray)/1.2, 0, 255)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
# hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (4, 4))
kernel3 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
kernel4 = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
kernel5 = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 8))
kernel6 = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 2))
kernel7 = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 8))
adaptivekernel = None
# return lab[:,:,2]
currentimgofatype = lab[:,:,0] # L-channel: expresses the brightness in the image
# imglist = []
Bthresh = cv2.adaptiveThreshold(currentimgofatype, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 201, 35)
# return Bthresh
contours, heirarchy = cv2.findContours(255-Bthresh,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# imgcopy = cv2.drawContours(imgcopy, contours, -1, color=(0,255,0), thickness=1)
# return imgcopy
boundingboxes = np.empty((len(contours), 4), dtype=int)
for i, contour in enumerate(contours):
b = cv2.boundingRect(contour)
boundingboxes[i] = b
# imgcopy = cv2.rectangle(imgcopy, (b[0],b[1]), (b[0]+b[2], b[1]+b[3]), 128, thickness=3)
# return imgcopy
epsilonvalue = np.median(boundingboxes, axis=0)[3]
adaptivekernel = cv2.getStructuringElement(cv2.MORPH_RECT, (int(epsilonvalue/15), int(epsilonvalue/15)))
# imglist.append(Bthresh)
# imglist.append(255-Bthresh)
morphedBthresh = cv2.morphologyEx(Bthresh, cv2.MORPH_DILATE, kernel3, iterations=2)
# morphedBthresh = cv2.morphologyEx(Bthresh, cv2.MORPH_DILATE, adaptivekernel, iterations=2)
goodmorphBthresh = cv2.morphologyEx(Bthresh, cv2.MORPH_ERODE, kernel4, iterations=2)
# goodmorphBthresh = cv2.morphologyEx(Bthresh, cv2.MORPH_ERODE, adaptivekernel, iterations=3)
# morphedBthresh = cv2.morphologyEx(morphedBthresh, cv2.MORPH_DILATE, kernel7)
# imglist.append(morphedBthresh)
# imglist.append(goodmorphBthresh)
thresh = cv2.threshold(currentimgofatype, 0, 255, cv2.THRESH_OTSU)[1]
# imglist.append(thresh)
morphedthresh = cv2.morphologyEx(thresh, cv2.MORPH_ERODE, kernel6)
morphedthresh = cv2.morphologyEx(morphedthresh, cv2.MORPH_ERODE, kernel7)
reducedthresh = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, adaptivekernel, iterations=1)
# imglist.append(morphedthresh)
# imglist.append(reducedthresh)
anded1 = cv2.bitwise_and(255-Bthresh, morphedthresh)
anded2 = cv2.bitwise_and(reducedthresh, 255-morphedthresh)
# imglist.append(anded1)
# imglist.append(anded2)
contours, other = cv2.findContours(anded2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# print(other)
mask = np.full(gray.shape,fill_value=255, dtype=np.uint8)
for i, contour in enumerate(contours):
if (other[0][i][2] != -1 and other[0][i][3] == -1):
b = cv2.boundingRect(contour)
# image = cv2.rectangle(image, (b[0],b[1]), (b[0]+b[2], b[1]+b[3]), (0,255,0), thickness=3)
mask = cv2.rectangle(mask, (b[0],b[1]), (b[0]+b[2], b[1]+b[3]), 0, thickness=cv2.FILLED)
# bingus = cv2.bitwise_or(goodmorphBthresh, mask)
bingus = cv2.bitwise_or(Bthresh, mask)
# bingus = cv2.morphologyEx(bingus, cv2.MORPH_CLOSE, adaptivekernel)
# imglist.append(bingus)
# return imglist
return bingus
## ------------------------------specific to row summation deskewing------------------------------
def sum_rows(img):
# Create a list to store the row sums
row_sums = []
# Iterate through the rows
for r in range(img.shape[0]-1):
# Sum the row
row_sum = sum(sum(img[r:r+1,:]))
# Add the sum to the list
row_sums.append(row_sum)
# Normalize range to (0,255)
row_sums = (row_sums/max(row_sums)) * 255
# Return
return row_sums
## ------------------------------active functions------------------------------
## ------------------------------cropping------------------------------
def morphologyCrop(image, withRectangle=False):
# convert to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 170, 255, cv2.THRESH_BINARY)[1]
# apply morphology
kernel = np.ones((7,7), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((9,9), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_ERODE, kernel)
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = 0
for c in contours:
area = cv2.contourArea(c)
if area > area_thresh:
area_thresh = area
big_contour = c
# get bounding box
x,y,w,h = cv2.boundingRect(big_contour)
# draw filled contour on black background
mask = np.zeros_like(gray)
mask = cv2.merge([mask,mask,mask])
# mask = cv2.blur(mask,(121,121))
cv2.drawContours(mask, [big_contour], -1, (255,255,255), cv2.FILLED)
# apply mask to input
result1 = image.copy()
result1 = cv2.bitwise_and(result1, mask)
# crop result
result2 = result1[y:y+h, x:x+w]
if (withRectangle):
return result2, (x,y,w,h)
return result2
##### ------------------------------TEST CODE FOR SELECTIVESEARCHCROP------------------------------
# ## Test this code for the masking/colour squishing. it essentially can just speed up clipping the edges.
# #!/usr/local/bin/python3
# import cv2 as cv
# import numpy as np
# # Load the aerial image and convert to HSV colourspace
# image = cv.imread("aerial.png")
# hsv=cv.cvtColor(image,cv.COLOR_BGR2HSV)
# # Define lower and uppper limits of what we call "brown"
# brown_lo=np.array([10,0,0])
# brown_hi=np.array([20,255,255])
# # Mask image to only select browns
# mask=cv.inRange(hsv,brown_lo,brown_hi)
# # Change image to red where we found brown
# image[mask>0]=(0,0,255)
# cv.imwrite("result.png",image)
#CAN ALSO TRY USING NUMPY VECTORIZATION
#------------------------------------------------------------------------------------------
def selectiveSearchCrop(image):
img, scale = ResizeWithAspectRatio(image,300, retscale=True)
rects = selectiveSearchSegmentationImp(cv2.GaussianBlur(img, (15,15),0))
bigRects = biggestRects(20, rects)
overlaprectangle = overlapRect(bigRects)
if (overlaprectangle[0] == -1):
# print("hi")
return image
# print(image.shape)
finalrect = (int(overlaprectangle[0]*scale), int(overlaprectangle[1]*scale), int(overlaprectangle[2]*scale), int(overlaprectangle[3]*scale))
# print(finalrect)
return image[finalrect[0]: finalrect[0]+finalrect[2], finalrect[1]: finalrect[1]+finalrect[3], :]
def cannyEdgeCrop(image, lower = 100, upper = 255, threshold1 = 50, threshold2 = 350):
lower = max(0,lower)
upper = min(255, upper)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
scaled_gray = np.zeros(gray.shape, gray.dtype)
# for y in range(0,gray.shape[0]):
# for x in range(0,gray.shape[1]):
# scaled_gray[y][x] = colourscaler(gray[y][x], lower, upper)
scaled_gray = gray
blurred = cv2.GaussianBlur(scaled_gray, (15,15),0)
edged = cv2.Canny(blurred, threshold1, threshold2)
return edged
def houghlineCrop(image):
prepped = premorphCrop(image)
prepped = ResizeWithAspectRatio(prepped,1000)
# kernel = np.ones((5,5), np.uint8)
# prepped = cv2.dilate(prepped, kernel, iterations=1)
gray1 = cv2.cvtColor(prepped, cv2.COLOR_BGR2GRAY)
dst1 = cv2.Canny(gray1, 0, 500, None, 3)
cdstP = prepped.copy()
cdstPmargin = cdstP.copy()
linesP = cv2.HoughLinesP(dst1, 1, np.pi / 180, 30, None, 80, 30)
vmarginlines = WithinXDegrees(linesP, 7)
hmarginlines = WithinXDegrees(linesP, 7, baseangle=90)
vrect = lineBoundingRect(vmarginlines,asRect=False, returnint=True)
hmarginlines = lineswithinrange(hmarginlines, (vrect[0], vrect[1]), (vrect[2],vrect[3]), x=True, y=False)
# print(hmarginlines)
if (hmarginlines != []):
marginlines = np.append(vmarginlines, hmarginlines, axis=0)
else:
marginlines = vmarginlines
# print(marginlines)
rect = lineBoundingRect(marginlines,asRect=False, returnint=True)
# print(rect)
# cdstP = cv2.rectangle(cdstP, (rect[0],rect[1]), (rect[2],rect[3]), (0,255,0), 3)
# print(cdstP.shape)
cropped = cdstP[rect[1]:rect[3], rect[0]:rect[2],:]
# if marginlines is not None:
# for i in range(0, len(marginlines)):
# l = marginlines[i]
# cv2.line(cdstP, (int(l[0]), int(l[1])), (int(l[2]), int(l[3])), (255,0,0), 3, cv2.LINE_AA)
return cropped
## ------------------------------deskewing------------------------------
def rowsumdeskew(image, withangle=False):
src = 255 - cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
scores = []
# # square the image
# h,w = src.shape
# small_dimention = min(h,w)
# src = src[:small_dimention, :small_dimention]
src = squarepad(src, fill=255)
src = cv2.threshold(src, 70, 255, cv2.THRESH_BINARY)[1]
src = ResizeWithAspectRatio(src, height=250)
angle = 0
finalangle = 0
while angle <= 360:
# Rotate the source image
img = rotate(src, angle)
# Crop the center 1/3rd of the image (roi is filled with text)
h,w = img.shape
buffer = min(h, w) - int(min(h,w)/1.5)
roi = img[int(h/2-buffer):int(h/2+buffer), int(w/2-buffer):int(w/2+buffer)]
# # Create background to draw transform on
# bg = np.zeros((buffer*2, buffer*2), np.uint8)
# Compute the sums of the rows
row_sums = sum_rows(roi)
# High score --> Zebra stripes
score = np.count_nonzero(row_sums)
scores.append(score)
# othercount = othercount + 1
# Image has best rotation
if score <= min(scores):
# count = count + 1
# Save the rotatied image
# print('found optimal rotation')
# best_rotation = img.copy()
finalangle = angle
# goodangle = angle
# k = display_data(roi, row_sums, buffer)
# if k == 27: break
# Increment angle and try again
angle += .75
# cv2.destroyAllWindows()
if (withangle):
return rotate(image,finalangle), finalangle
return rotate(image, finalangle)
def externaldeskew(image, fill=(0,0,0), alreadygray=False):
# image = io.imread(_img)
# print(type(image))
if (alreadygray):
grayscale = image.copy()
else:
grayscale = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
grayscale = squarepad(grayscale,fill=255)
grayscale = ResizeWithAspectRatio(grayscale, height=300)
# print(type(grayscale))
angle = determine_skew(grayscale)
# print(angle)
rotated = rotate(image, angle, fill=fill)
return rotated
def getreceipttextAngle(cvImage) -> float:
# Prep image, copy, convert to gray scale, blur, and threshold
newImage = padWithColour(cvImage, hpadding=50, vpadding=50, fill=(255,255,255))
# return newImage
gray = cv2.cvtColor(newImage, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9, 9), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Apply dilate to merge text into meaningful lines/paragraphs.
# Use larger kernel on X axis to merge characters into single line, cancelling out any spaces.
# But use smaller kernel on Y axis to separate between different blocks of text
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 5))
dilate = cv2.dilate(thresh, kernel, iterations=5)
# return dilate
# Find all contours
contours, hierarchy = cv2.findContours(dilate, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key = cv2.contourArea, reverse = True)
# Find largest contour and surround in min area box
largestContour = contours[0]
mergedcontour = mergecontours(contours)
# return cv2.drawContours(newImage, [mergedcontour], -1, (0,255,0), thickness=3)
minAreaRect = cv2.minAreaRect(mergedcontour)
minAreaRect = list(minAreaRect)
minAreaRect[1] = list(minAreaRect[1])
if (minAreaRect[1][0] > minAreaRect[1][1]):
temp = minAreaRect[1][0]
minAreaRect[1][0] = minAreaRect[1][1]
minAreaRect[1][1] = temp
minAreaRect[2] -= 90
# return cv2.drawContours(newImage, [largestContour], -1, (0,255,0), thickness=3)
# minAreaRect = cv2.minAreaRect(largestContour)
box = cv2.boxPoints(minAreaRect)
box = np.intp(box)
newImage = cv2.drawContours(newImage, [box], -1, (0,255,0), thickness=3)
# return newImage
# Determine the angle. Convert it to the value that was originally used to obtain skewed image
angle = minAreaRect[-1]
# print(angle)
angle = anglecorrector(angle)+90
# print(angle)
return angle
def receipttextdeskew(img, fill=(0,0,0), returnangle=False):
colourimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
angle = getreceipttextAngle(colourimg)
if returnangle:
return angle
# padimg = padWithColour(img, hpadding=50, vpadding=50, fill=fill)
# print(img.shape)
# grayfill = int((fill[0]*0.299) + (fill[1]*0.587) + (fill[2]*0.114))
rotated = rotatewithexactpadding(colourimg, angle, fill=fill)
grayrotated = cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY)
# print(grayrotated)
croprect = croptoblack(grayrotated, returnrect=True)
# rotated = cv2.cvtColor(rotated, cv2.COLOR_GRAY2BGR)
rotated = rotated[croprect[1]:croprect[1]+croprect[3], croprect[0]:croprect[0]+croprect[2], :]
rotated = padWithColour(rotated, hpadding=50, vpadding=50, fill=fill)
return rotated
## ------------------------------Full deskewing and cropping------------------------------
def houghlineprocessing(image):
croppedanddeskewed, angle = houghlinedeskewandcrop(image)
# return croppedanddeskewed
# postprocessed = cropclarifying(croppedanddeskewed)
postprocessed = croppedanddeskewed
# return postprocessed
# postprocessed = mf.croptoblack(postprocessed)
# postprocessed = cv2.cvtColor(postprocessed, cv2.COLOR_GRAY2BGR)
# return postprocessed
# final = mf.externaldeskew(postprocessed, fill=(255,255,255))
# rotangle = mf.receipttextdeskew(postprocessed, fill=(255,255,255), returnangle=True)
final = postprocessed
# final = mf.croptoblack(final)
# cv2.imshow("postprocessed", mf.ResizeWithAspectRatio(postprocessed, 1000))
# cv2.imshow("final", mf.ResizeWithAspectRatio(final, 1000))
# cv2.waitKey(0)
# cv2.destroyAllWindows()
return final
###### DESIRE: CONVERT STUFF RELATED TO THE HOUGHLINE PROCESSING INTO C SINCE IT ONLY REALLY USES OPENCV