|
from typing import List |
|
|
|
import cv2 |
|
import numpy as np |
|
|
|
from PIL import Image, ImageDraw |
|
|
|
from surya.postprocessing.util import get_line_angle, rescale_bbox |
|
from surya.schema import ColumnLine |
|
|
|
|
|
def get_detected_lines_sobel(image, vertical=True): |
|
|
|
if vertical: |
|
dx = 1 |
|
dy = 0 |
|
else: |
|
dx = 0 |
|
dy = 1 |
|
|
|
sobelx = cv2.Sobel(image, cv2.CV_32F, dx, dy, ksize=3) |
|
|
|
|
|
|
|
abs_sobelx = np.absolute(sobelx) |
|
|
|
|
|
scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx)) |
|
|
|
kernel = np.ones((20, 1), np.uint8) |
|
eroded = cv2.erode(scaled_sobel, kernel, iterations=1) |
|
scaled_sobel = cv2.dilate(eroded, kernel, iterations=3) |
|
|
|
return scaled_sobel |
|
|
|
|
|
def get_detected_lines(image, slope_tol_deg=2, vertical=False, horizontal=False) -> List[ColumnLine]: |
|
assert not (vertical and horizontal) |
|
new_image = image.astype(np.float32) * 255 |
|
if vertical or horizontal: |
|
new_image = get_detected_lines_sobel(new_image, vertical) |
|
new_image = new_image.astype(np.uint8) |
|
|
|
edges = cv2.Canny(new_image, 150, 200, apertureSize=3) |
|
if vertical: |
|
max_gap = 100 |
|
min_length = 10 |
|
else: |
|
max_gap = 10 |
|
min_length = 4 |
|
|
|
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=150, minLineLength=min_length, maxLineGap=max_gap) |
|
|
|
line_info = [] |
|
if lines is not None: |
|
for line in lines: |
|
vertical_line = False |
|
horizontal_line = False |
|
x1, y1, x2, y2 = line[0] |
|
bbox = [x1, y1, x2, y2] |
|
|
|
if x2 == x1: |
|
vertical_line = True |
|
else: |
|
line_angle = get_line_angle(x1, y1, x2, y2) |
|
if 90 - slope_tol_deg < line_angle < 90 + slope_tol_deg: |
|
vertical_line = True |
|
elif -90 - slope_tol_deg < line_angle < -90 + slope_tol_deg: |
|
vertical_line = True |
|
elif -slope_tol_deg < line_angle < slope_tol_deg: |
|
horizontal_line = True |
|
|
|
if bbox[3] < bbox[1]: |
|
bbox[1], bbox[3] = bbox[3], bbox[1] |
|
if bbox[2] < bbox[0]: |
|
bbox[0], bbox[2] = bbox[2], bbox[0] |
|
row = ColumnLine(bbox=bbox, vertical=vertical_line, horizontal=horizontal_line) |
|
line_info.append(row) |
|
|
|
if vertical: |
|
line_info = [line for line in line_info if line.vertical] |
|
|
|
if horizontal: |
|
line_info = [line for line in line_info if line.horizontal] |
|
|
|
return line_info |
|
|
|
|
|
def draw_lines_on_image(line_info: List[ColumnLine], img): |
|
draw = ImageDraw.Draw(img) |
|
|
|
for line in line_info: |
|
divisor = 20 |
|
if line.horizontal: |
|
divisor = 200 |
|
x1, y1, x2, y2 = [x // divisor * divisor for x in line.bbox] |
|
if line.vertical: |
|
draw.line((x1, y1, x2, y2), fill="red", width=3) |
|
|
|
return img |
|
|
|
|
|
def get_vertical_lines(image, processor_size, image_size, divisor=20, x_tolerance=40, y_tolerance=20) -> List[ColumnLine]: |
|
vertical_lines = get_detected_lines(image, vertical=True) |
|
for line in vertical_lines: |
|
line.rescale_bbox(processor_size, image_size) |
|
vertical_lines = sorted(vertical_lines, key=lambda x: x.bbox[0]) |
|
for line in vertical_lines: |
|
line.round_bbox(divisor) |
|
|
|
|
|
to_remove = [] |
|
for i, line in enumerate(vertical_lines): |
|
for j, line2 in enumerate(vertical_lines): |
|
if j <= i: |
|
continue |
|
if line.bbox[0] != line2.bbox[0]: |
|
continue |
|
|
|
expanded_line1 = [line.bbox[0], line.bbox[1] - y_tolerance, line.bbox[2], |
|
line.bbox[3] + y_tolerance] |
|
|
|
line1_points = set(range(int(expanded_line1[1]), int(expanded_line1[3]))) |
|
line2_points = set(range(int(line2.bbox[1]), int(line2.bbox[3]))) |
|
intersect_y = len(line1_points.intersection(line2_points)) > 0 |
|
|
|
if intersect_y: |
|
vertical_lines[j].bbox[1] = min(line.bbox[1], line2.bbox[1]) |
|
vertical_lines[j].bbox[3] = max(line.bbox[3], line2.bbox[3]) |
|
to_remove.append(i) |
|
|
|
vertical_lines = [line for i, line in enumerate(vertical_lines) if i not in to_remove] |
|
|
|
|
|
to_remove = [] |
|
for i, line in enumerate(vertical_lines): |
|
if i in to_remove: |
|
continue |
|
for j, line2 in enumerate(vertical_lines): |
|
if j <= i or j in to_remove: |
|
continue |
|
close_in_x = abs(line.bbox[0] - line2.bbox[0]) < x_tolerance |
|
line1_points = set(range(int(line.bbox[1]), int(line.bbox[3]))) |
|
line2_points = set(range(int(line2.bbox[1]), int(line2.bbox[3]))) |
|
|
|
intersect_y = len(line1_points.intersection(line2_points)) > 0 |
|
|
|
if close_in_x and intersect_y: |
|
|
|
if len(line2_points) > len(line1_points): |
|
vertical_lines[j].bbox[1] = min(line.bbox[1], line2.bbox[1]) |
|
vertical_lines[j].bbox[3] = max(line.bbox[3], line2.bbox[3]) |
|
to_remove.append(i) |
|
else: |
|
vertical_lines[i].bbox[1] = min(line.bbox[1], line2.bbox[1]) |
|
vertical_lines[i].bbox[3] = max(line.bbox[3], line2.bbox[3]) |
|
to_remove.append(j) |
|
|
|
vertical_lines = [line for i, line in enumerate(vertical_lines) if i not in to_remove] |
|
|
|
if len(vertical_lines) > 0: |
|
|
|
vertical_lines[0].bbox[1] = 0 |
|
|
|
return vertical_lines |