|
""" Calculates skew angle """ |
|
""" |
|
This code is based on the following file: |
|
https://github.com/kakul/Alyn/blob/master/alyn/skew_detect.py |
|
""" |
|
import os |
|
import optparse |
|
|
|
import numpy as np |
|
|
|
from skimage import io |
|
from skimage.feature import canny |
|
from skimage.transform import hough_line, hough_line_peaks |
|
import cv2 |
|
|
|
|
|
class SkewDetect: |
|
|
|
piby4 = np.pi / 4 |
|
|
|
def __init__( |
|
self, |
|
input_file=None, |
|
output_file=None, |
|
sigma=0.50, |
|
display_output=None, |
|
num_peaks=20, |
|
skew_max=4.0, |
|
acc_deg=0.5, |
|
roi_w=1.0, |
|
roi_h=1.0, |
|
): |
|
|
|
self.sigma = sigma |
|
self.input_file = input_file |
|
self.output_file = output_file |
|
self.display_output = display_output |
|
self.num_peaks = num_peaks |
|
self.skew_max = skew_max |
|
self.acc_deg = acc_deg |
|
self.roi_w = roi_w |
|
self.roi_h = roi_h |
|
|
|
def write_to_file(self, wfile, data): |
|
|
|
for d in data: |
|
wfile.write(d + ': ' + str(data[d]) + '\n') |
|
wfile.write('\n') |
|
|
|
def get_max_freq_elem(self, arr): |
|
|
|
max_arr = [] |
|
freqs = {} |
|
for i in arr: |
|
if i in freqs: |
|
freqs[i] += 1 |
|
else: |
|
freqs[i] = 1 |
|
|
|
sorted_keys = sorted(freqs, key=freqs.get, reverse=True) |
|
max_freq = freqs[sorted_keys[0]] |
|
|
|
for k in sorted_keys: |
|
if freqs[k] == max_freq: |
|
max_arr.append(k) |
|
|
|
return max_arr |
|
|
|
def compare_sum(self, value): |
|
if value >= 44 and value <= 46: |
|
return True |
|
else: |
|
return False |
|
|
|
def display(self, data): |
|
|
|
for i in data: |
|
print(str(i) + ": " + str(data[i])) |
|
|
|
def calculate_deviation(self, angle): |
|
|
|
angle_in_degrees = np.abs(angle) |
|
deviation = np.abs(SkewDetect.piby4 - angle_in_degrees) |
|
|
|
return deviation |
|
|
|
def run(self): |
|
|
|
if self.display_output: |
|
if self.display_output.lower() == 'yes': |
|
self.display_output = True |
|
else: |
|
self.display_output = False |
|
|
|
if self.input_file is None: |
|
print("Invalid input, nothing to process.") |
|
else: |
|
self.process_single_file() |
|
|
|
def check_path(self, path): |
|
|
|
if os.path.isabs(path): |
|
full_path = path |
|
else: |
|
full_path = os.getcwd() + '/' + str(path) |
|
return full_path |
|
|
|
def process_single_file(self): |
|
|
|
file_path = self.check_path(self.input_file) |
|
res = self.determine_skew(file_path) |
|
|
|
if self.output_file: |
|
output_path = self.check_path(self.output_file) |
|
wfile = open(output_path, 'w') |
|
self.write_to_file(wfile, res) |
|
wfile.close() |
|
|
|
return res |
|
|
|
def determine_skew(self, img_file): |
|
|
|
img_ori = io.imread(img_file, as_gray=True) |
|
height, width = img_ori.shape |
|
img = img_ori[int(height*(0.5-self.roi_h/2.0)):int(height*(0.5+self.roi_h/2.0)), |
|
int(width * (0.5-self.roi_w/2.0)):int(width * (0.5+self.roi_w/2.0))] |
|
|
|
img = cv2.resize(img, (img.shape[1]//4, img.shape[0]//4)) |
|
|
|
edges = canny(img, sigma=self.sigma) |
|
range_rad = np.arange(-np.pi/2, -np.pi/2+np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg)) |
|
range_rad = np.concatenate( |
|
[range_rad, |
|
np.arange(-np.deg2rad(self.skew_max), np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
range_rad = np.concatenate( |
|
[range_rad, |
|
np.arange(np.pi/2-np.deg2rad(self.skew_max), np.pi/2, |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
|
|
h, a, d = hough_line(edges, theta=range_rad) |
|
|
|
th = 0.2 * h.max() |
|
_, ap, _ = hough_line_peaks( |
|
h, a, d, threshold=th, num_peaks=self.num_peaks) |
|
|
|
if len(ap) == 0: |
|
data = { |
|
"Image File": img_file, |
|
"Average Deviation from pi/4": 0.0, |
|
"Estimated Angle": 0.0, |
|
"Angle bins": [[], [], [], []], |
|
"Message": "Bad Quality"} |
|
return data |
|
|
|
absolute_deviations = [self.calculate_deviation(k) for k in ap] |
|
average_deviation = np.mean(np.rad2deg(absolute_deviations)) |
|
ap_deg = [np.rad2deg(x) for x in ap] |
|
|
|
for i in range(len(ap_deg)): |
|
if ap_deg[i] >= 45.0: |
|
ap_deg[i] -= 90.0 |
|
elif ap_deg[i] <= -45.0: |
|
ap_deg[i] += 90.0 |
|
|
|
bin_0_45 = [] |
|
bin_45_90 = [] |
|
bin_0_45n = [] |
|
bin_45_90n = [] |
|
|
|
for ang in ap_deg: |
|
|
|
deviation_sum = (90 - ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_45_90.append(ang) |
|
continue |
|
|
|
deviation_sum = (ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_0_45.append(ang) |
|
continue |
|
|
|
deviation_sum = (-ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_0_45n.append(ang) |
|
continue |
|
|
|
deviation_sum = (90 + ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_45_90n.append(ang) |
|
|
|
angles = [bin_0_45, bin_45_90, bin_0_45n, bin_45_90n] |
|
lmax = 0 |
|
|
|
for j in range(len(angles)): |
|
tmp_l = len(angles[j]) |
|
if tmp_l > lmax: |
|
lmax = tmp_l |
|
maxi = j |
|
|
|
if lmax: |
|
ans_arr = self.get_max_freq_elem(angles[maxi]) |
|
ans_res = np.mean(ans_arr) |
|
|
|
else: |
|
ans_arr = self.get_max_freq_elem(ap_deg) |
|
ans_res = np.mean(ans_arr) |
|
|
|
data = { |
|
"Image File": img_file, |
|
"Average Deviation from pi/4": average_deviation, |
|
"Estimated Angle": ans_res, |
|
"Angle bins": angles, |
|
"Message": "Successfully detected lines"} |
|
|
|
if self.display_output: |
|
self.display(data) |
|
|
|
return data |
|
|
|
def determine_skew_on_memory(self, img_data): |
|
|
|
img_ori = cv2.cvtColor(img_data, cv2.COLOR_BGR2GRAY) |
|
height, width = img_ori.shape |
|
img = img_ori[int(height*(0.5-self.roi_h/2.0)):int(height*(0.5+self.roi_h/2.0)), |
|
int(width * (0.5-self.roi_w/2.0)):int(width * (0.5+self.roi_w/2.0))] |
|
|
|
img = cv2.resize(img, (img.shape[1]//4, img.shape[0]//4)) |
|
|
|
edges = canny(img, sigma=self.sigma) |
|
range_rad = np.arange(-np.pi/2, -np.pi/2+np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg)) |
|
range_rad = np.concatenate([range_rad, |
|
np.arange(-np.deg2rad(self.skew_max), |
|
np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
range_rad = np.concatenate([range_rad, |
|
np.arange(np.pi/2-np.deg2rad(self.skew_max), |
|
np.pi/2, |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
|
|
h, a, d = hough_line(edges, theta=range_rad) |
|
|
|
th = 0.2 * h.max() |
|
_, ap, _ = hough_line_peaks( |
|
h, a, d, threshold=th, num_peaks=self.num_peaks) |
|
|
|
if len(ap) == 0: |
|
data = { |
|
"Average Deviation from pi/4": 0.0, |
|
"Estimated Angle": 0.0, |
|
"Angle bins": [[], [], [], []], |
|
"Message": "Bad Quality"} |
|
return data |
|
|
|
absolute_deviations = [self.calculate_deviation(k) for k in ap] |
|
average_deviation = np.mean(np.rad2deg(absolute_deviations)) |
|
ap_deg = [np.rad2deg(x) for x in ap] |
|
|
|
for i in range(len(ap_deg)): |
|
if ap_deg[i] >= 45.0: |
|
ap_deg[i] -= 90.0 |
|
elif ap_deg[i] <= -45.0: |
|
ap_deg[i] += 90.0 |
|
|
|
bin_0_45 = [] |
|
bin_45_90 = [] |
|
bin_0_45n = [] |
|
bin_45_90n = [] |
|
|
|
for ang in ap_deg: |
|
|
|
deviation_sum = (90 - ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_45_90.append(ang) |
|
continue |
|
|
|
deviation_sum = (ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_0_45.append(ang) |
|
continue |
|
|
|
deviation_sum = (-ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_0_45n.append(ang) |
|
continue |
|
|
|
deviation_sum = (90 + ang + average_deviation) |
|
if self.compare_sum(deviation_sum): |
|
bin_45_90n.append(ang) |
|
|
|
angles = [bin_0_45, bin_45_90, bin_0_45n, bin_45_90n] |
|
lmax = 0 |
|
|
|
for j in range(len(angles)): |
|
tmp_l = len(angles[j]) |
|
if tmp_l > lmax: |
|
lmax = tmp_l |
|
maxi = j |
|
|
|
if lmax: |
|
ans_arr = self.get_max_freq_elem(angles[maxi]) |
|
ans_res = np.mean(ans_arr) |
|
|
|
else: |
|
ans_arr = self.get_max_freq_elem(ap_deg) |
|
ans_res = np.mean(ans_arr) |
|
|
|
data = { |
|
"Average Deviation from pi/4": average_deviation, |
|
"Estimated Angle": ans_res, |
|
"Angle bins": angles, |
|
"Message": "Successfully detected lines"} |
|
|
|
return data |
|
|
|
def determine_line(self, img_file): |
|
|
|
img_ori = io.imread(img_file, as_gray=True) |
|
height, width = img_ori.shape |
|
img = img_ori[int(height*(0.5-self.roi_h/2.0)):int(height*(0.5+self.roi_h/2.0)), |
|
int(width * (0.5-self.roi_w/2.0)):int(width * (0.5+self.roi_w/2.0))] |
|
edges = canny(img, sigma=self.sigma) |
|
range_rad = np.arange(-np.pi/2, -np.pi/2+np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg)) |
|
range_rad = np.concatenate([range_rad, |
|
np.arange(-np.deg2rad(self.skew_max), |
|
np.deg2rad(self.skew_max), |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
range_rad = np.concatenate([range_rad, |
|
np.arange(np.pi/2-np.deg2rad(self.skew_max), np.pi/2, |
|
step=np.deg2rad(self.acc_deg))], |
|
axis=0) |
|
|
|
h, a, d = hough_line(edges, theta=range_rad) |
|
|
|
th = 0.2 * h.max() |
|
ac, ap, d = hough_line_peaks( |
|
h, a, d, threshold=th, num_peaks=self.num_peaks) |
|
|
|
return ac, ap, d |
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
parser = optparse.OptionParser() |
|
|
|
parser.add_option( |
|
'-d', '--display', |
|
default=None, |
|
dest='display_output', |
|
help='Display logs') |
|
parser.add_option( |
|
'-i', '--input', |
|
default=None, |
|
dest='input_file', |
|
help='Input file name') |
|
parser.add_option( |
|
'-o', '--output', |
|
default=None, |
|
dest='output_file', |
|
help='Output file name') |
|
parser.add_option( |
|
'-p', '--plot', |
|
default=None, |
|
dest='plot_hough', |
|
help='Plot the Hough Transform') |
|
parser.add_option( |
|
'-s', '--sigma', |
|
default=3.0, |
|
dest='sigma', |
|
help='Sigma for Canny Edge Detection', |
|
type=float) |
|
options, args = parser.parse_args() |
|
skew_obj = SkewDetect( |
|
options.input_file, |
|
options.output_file, |
|
options.sigma, |
|
options.display_output, |
|
options.num_peaks, |
|
options.plot_hough) |
|
skew_obj.run() |
|
|