Accidentally uses mf. inside the file for module usage Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
1178 lines
42 KiB
Python
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 |