# -*- coding: utf-8 -*-
##------------------------------------------
## My Library Image tilt correction with OpenCV
##
## 2022.03.07 Masahiro Izutsu
##------------------------------------------
## mylib_tilt.py
## 2022/03/09 ver 0.03 update
title = 'Image tilt correction with OpenCV'
# Color Escape Code
GREEN = '\033[1;32m'
RED = '\033[1;31m'
NOCOLOR = '\033[0m'
YELLOW = '\033[1;33m'
# 定数定義
from os.path import expanduser
DEF_OUTPUT_DIR = expanduser('result/')
# import処理
import sys
import os
import cv2
import numpy as np
import argparse
import math
from scipy import ndimage
from tkinter import filedialog
import tkinter
import mylib_file
# Parses arguments for the application
def parse_args():
# タイトル・バージョン情報
print(GREEN)
print('--- {} ---'.format(title))
print(' OpenCV version {} '.format(cv2.__version__))
print(NOCOLOR)
parser = argparse.ArgumentParser()
parser.add_argument('--thr', metavar = 'THRESHOLD',
default = 100,
help = 'Line threshold. Default value is 100')
parser.add_argument('--lln', metavar = 'MINLINELENGHT',
default = 200,
help = 'MinLineLength. Default value is 200')
parser.add_argument('--gap', metavar = 'MAXLINEGAP',
default = 20,
help = 'MaxLineGap(0=auto). Default value is 20')
parser.add_argument('--log', metavar = 'LOG',
default = 'n',
help = 'Log flag.(y/n) Default value is \'n\'')
parser.add_argument('-o', '--out', metavar = 'IMAGE_OUT',
default = 'non',
help = 'Processed image file directory \'xxxx/\' or \'non\'. Default value is \'non\'')
parser.add_argument('--mlt', metavar = 'MULTI',
default = 'n',
help = 'Multi flag.(y/n) Default value is \'n\'')
return parser
# 設定情報の表示
def display_info(thr, lln, gap, logflg, outdir, mltflg):
print(YELLOW + title + ': Starting application...' + NOCOLOR)
print(' - ' + YELLOW + 'Threshold : ' + NOCOLOR, thr)
print(' - ' + YELLOW + 'MinLineLength: ' + NOCOLOR, lln)
print(' - ' + YELLOW + 'MaxLineGap : ' + NOCOLOR, gap)
print(' - ' + YELLOW + 'Log flag : ' + NOCOLOR, logflg)
print(' - ' + YELLOW + 'Out directory: ' + NOCOLOR, outdir)
print(' - ' + YELLOW + 'Multi flag : ' + NOCOLOR, mltflg)
print('')
# 二点間の距離を求める
def get_distance(x1, y1, x2, y2):
d = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
return d
# 画像の回転(OpenCV)
def rotate_img(img, angle):
# 画像サイズ(横, 縦)から中心座標を求める
size = tuple([img.shape[1], img.shape[0]])
center = tuple([size[0] // 2, size[1] // 2])
# 回転の変換行列を求める(画像の中心, 回転角度, 拡大率)
mat = cv2.getRotationMatrix2D(center, angle, scale=1.0)
# アフィン変換(画像, 変換行列, 出力サイズ, 補完アルゴリズム)
rot_img = cv2.warpAffine(img, mat, size, flags=cv2.INTER_CUBIC,borderValue=(255, 255, 255))
return rot_img
# 画像の傾き検出
# @return 水平からの傾き角度
def get_degree(img, thr, lln, gap, f_name, f_ext, outdir, logflag = False):
l_img = img.copy()
img0 = img.copy()
gray_image = cv2.cvtColor(l_img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray_image,50,150,apertureSize = 3)
lines = cv2.HoughLinesP(edges, 1, np.pi/360, thr, lln, gap)
sum_arg = 0.0;
arg = 0.0
arg_lst = []
count = 0;
for line in lines:
for x1,y1,x2,y2 in line:
# 直線を描画する。
xs = x2 - x1
ys = y2 - y1
cv2.line(img0, (x1, y1), (x2, y2), (0, 0, 255), 2)
arg = math.degrees(math.atan2((y2-y1), (x2-x1)))
r = get_distance(x1, y1, x2, y2)
if r > 40: # 距離の小さいものは除く
HORIZONTAL = 0
DIFF = 20 # 許容誤差 -> -20 - +20 を本来の水平線と考える
#arg != 0を条件に追加し、傾きの平均を0に寄りにくくした。
if arg != 0 and arg > HORIZONTAL - DIFF and arg < HORIZONTAL + DIFF :
cv2.line(img0, (x1, y1), (x2, y2), (255, 0, 0), 2)
arg_lst.append(arg)
sum_arg += arg;
count += 1
if logflag:
print(' count= {}, r= {}, degree= {}'.format(count, r, arg))
# 処理結果1
if outdir != 'non' and logflag:
cv2.imwrite(f_name + '_tilt_gray' + f_ext, gray_image)
cv2.imwrite(f_name + '_tilt_edge' + f_ext, edges)
cv2.imwrite(f_name + '_tilt_line' + f_ext, img0)
# 誤検出チェック
du = 0.0 # 正のデータ
dd = 0.0 # 負のデータ
for d in arg_lst:
if d > 0:
du = (du + d) / 2
else:
dd = (dd + d) / 2
han = abs(abs(du) - abs(dd)) # 正負の差
if du != 0 and dd != 0 and han > 0.1:
return HORIZONTAL # 差の大きいものは除外
if count < 6: # 5個以下のデータの場合は除外する
return HORIZONTAL
else:
return (sum_arg / count); # 座標系の違いからこの値が補正すべき角度になる
def tilt_image(filename, thr, lln, gap, outdir, logflag = False, log2 = False):
# 拡張子なしの出力ファイル名
basename = os.path.basename(filename)
f_base = os.path.splitext(basename)[0]
f_ext = os.path.splitext(basename)[1]
f_name = outdir + f_base
f_tilt = os.path.dirname(filename) + '/' + f_base + '_tilt' + f_ext
if log2:
print(GREEN, 'Image file: <<{}>>'.format(basename), NOCOLOR)
# OpenCV でイメージを読む
img = cv2.imread(filename)
if img is None:
print(RED + "\n Unable to read the input." + NOCOLOR)
return False
# 傾き補正
arg = get_degree(img, thr, lln, gap, f_name, f_ext, outdir, logflag)
if arg != 0:
tilt_img = rotate_img(img, arg) # OpenCVによる画像回転(高速)
# オリジナル画像の保存 org/
my_file = mylib_file.FileTreatment(False)
my_file.save_file(filename)
cv2.imwrite(f_tilt, tilt_img)
if log2:
print(YELLOW + ' New file: ', f_base + '_tilt' + f_ext + NOCOLOR)
if log2:
print(' Detection tilt angle: ', arg)
return True
def tilt_image_fol(dirname, thr, lln, gap, outdir, logflag = False, log2 = False):
my_file = mylib_file.FileTreatment(False)
img_ext = {'.bmp', '.png', '.jpeg', '.jpg', '.tif'}
imglist = my_file.get_file_list_sel(dirname + '/*', img_ext)
ret = True
for filename in imglist:
if not tilt_image(filename, thr, lln, gap, outdir, logflag, True):
ret = False
break
return ret
# ** main関数 **
def main():
# Tk オブジェクトのインスタンス作成
root = tkinter.Tk()
# Tk のウィンドウを非表示にする
root.withdraw()
# Argument parsing and parameter setting
ARGS = parse_args().parse_args()
thr = int(ARGS.thr)
lln = int(ARGS.lln)
gap = int(ARGS.gap)
logflg = ARGS.log
logflag = True if logflg == 'y' else False
outdir = ARGS.out
mltflg = ARGS.mlt
mltflag = True if mltflg == 'y' else False
# 情報表示
display_info(thr, lln, gap, logflg, outdir, mltflg)
if mltflag:
# 指定フォルダ内の画像ファイルを一括処理する
dirname = filedialog.askdirectory(initialdir = './', title = '対象ディレクトリを選択', mustexist = True)
if len(dirname) > 0:
tilt_image_fol(dirname, thr, lln, gap, outdir, logflag, True)
else:
# 画像ファイルの選択
filename = filedialog.askopenfilename(title = '画像ファイルの選択', filetypes = [("Image file", ".bmp .png .jpeg .jpg .tif"), ("Bitmap", ".bmp"), ("PNG", ".png"), ("JPEG", ".jpeg"), ("JPG", ".jpg")], initialdir = './')
if len(filename) > 0:
tilt_image(filename, thr, lln, gap, outdir, logflag, True)
print('\n Finished.')
# main関数エントリーポイント(実行開始)
if __name__ == "__main__":
sys.exit(main())