概要
最近授業で画像処理を学び,行列変形で平面を変形させる技術を学びました
すると不意にピサの斜塔を垂直にしたい衝動にかられたのでPythonとOpenCVの練習がてらいざ作ってみようと思います
まぁほとんど理解できてないのでOpenCVの便利な関数達におんぶにだっこなんですけどね
それでは,Let's Vertical!
環境
windows10
Python 3.7.7
OpenCV 3.4.2
Pillow 7.1.2
numpy 1.18.4
実装
全体的に白が強いので今回はエッジではなく色調で輪郭を検出してみる
(エッジでうまくいかなかった背景は言うまでもない)
# 白色の検出
def detect_white_color(img):
# HSV色空間に変換
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 白色のHSVの値域
hsv_min = np.array([0,0,100])
hsv_max = np.array([180,45,255])
mask = cv2.inRange(hsv, hsv_min, hsv_max)
# マスキング処理
masked_img = cv2.bitwise_and(img, img, mask=mask)
return mask, masked_img
左がmaskで, 右がmasked_img
次に,二値化して矩形の検出
imgray = cv2.cvtColor(blurred_img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,115,255,0)
im, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0,255,0), 2)
まずrectにminAreaRect(contours)を実行
minAreaRect()はBox2Dの構造をもつタプルを返す
尚,Box2Dの構造は(左上の点(x,y),横と縦のサイズ(width, height),回転角)
長方形を描画するためには,これらの情報を4つの座標に変換する必要があるので,それができるboxPoints()を実行す
最後に描画するという流れになっている
(参照リファレンス)
うまくいってそうだけど他の写真にもうまく対応できるように念のためにガウシアンフィルターでぼかしておく!
# ガウシアンフィルターでぼかす
blurred_img = cv2.GaussianBlur(white_masked_img,(5,5),10)
かなりエッジが落ち着いてくれましたね
一番大きいrectだけでいいんでピックアップします
# 面積が最大の長方形のインデックスを返す関数
def detect_max_rect(img,contours):
#最大面積をとるcontoursのインデックス
max_idx = 0
for i in range(len(contours)):
if cv2.contourArea(contours[max_idx]) < cv2.contourArea(contours[i]):
max_idx = i
return max_idx
contourArea(contours)でコーナーに囲まれている四角形の面積を取得することができる
これを用いて面積が最大になる座標のインデックスを返す関数を定義する
そしてminAreaRect(max_idx)とすることで最大面積の四角形のみ抽出することができる
傾いてるね!
Let's Vertical
フローチャートとか書かずにやっているのでグダグダですみません
回転矩形のトリミング
まずは後々必要そうな四角領域をトリミングする関数を作成します
# 切り出し関数
def crop_rect(img, rect):
center, size, angle = rect
center = tuple(map(int, center)) # float -> int
size = tuple(map(int, size)) # float -> int
h, w = img.shape[:2] # 画像の高さ、幅
#rectの高さと幅を取得
height,width=rect[1]
height,width=int(height),int(width)
#angleは水平方向の線とのなす角を示すので,+90している
if angle<0:
angle+=90
# affine行列を用いて画像を回転させる
M = cv2.getRotationMatrix2D(center, angle, 1)
rotated = cv2.warpAffine(img, M, (w, h))
# 切り抜く
cropped = cv2.getRectSubPix(rotated, (w,h), center)
return cropped
詳細はコメントに書いておきました
実行結果の画像は以下
不自然で面白いですね
おったてる
angleが得られているのでもう簡単ですね
画像を回転させる関数を作成します
# 回転させる関数
def rotate_img(img,rect):
#高さ,幅から画面の中心を計算
_height = img.shape[0]
_width = img.shape[1]
_center = (int(_width/2), int(_height/2))
_angle=rect[2]
#angleが水平方向の線とのなす角を示すので,+90している
if _angle<0:
_angle+=90
# affine行列を用いて画像を回転させる
_M = cv2.getRotationMatrix2D(_center, _angle, 1)
_rotated_img = cv2.warpAffine(img, _M, (_width, _height))
return _rotated_img
画像の中心とってそこを軸にピサの角度分だけ回転させます
ハイ完璧!!!
...冗談です,すみません
さっきの関数はどっか捨てておいてください
方針としては元画像からくりぬいたところを画像補完して同じ座標に先ほどのトリムした画像を貼り付けようと思います
画像補完
# 画像補完
def inpaint_img(img):
_mask = cv2.imread('img/pisa1.jpg')
#白で塗りつぶす
cv2.drawContours(_mask, [box], 0, (255,255,255), -1)
_maskGray = cv2.cvtColor(_mask,cv2.COLOR_BGR2GRAY)
ret,_thresh = cv2.threshold(_maskGray,254,255,0)
#画像補正
dst = cv2.inpaint(img, _thresh, 3, cv2.INPAINT_TELEA)
cv2.imwrite('img/pisa_rect_inpaint.jpg', dst)
inpaintは入力画像とそれと同じサイズのマスク画像が必要です
このマスク画像中の非ゼロの値を持つ画素が修復するべき場所を表しています
すなわち,修復したい箇所を白色にして,残りは黒色に二値化処理することで補完することができます
アルゴリズムはINPAINT_TELEAとINPAINT_NSの2種類あり,今回は両方とも実行してみました
左がINPAINT_TELEA,右がINPAINT_NS
NSのほうが空の部分が少しだけきれいな気がするので今回はこっちを採用
おったてる(Take2)
画像の貼り付けはOpenCVだけだとめんどくさそうなのでPillowライブラリを使う
# トリミングしたピサを補完した背景画像に貼り付ける
def paste_pisa(rect):
#x,yにトリムしたピサのcenter座標が入る
x,y=rect[0]
#左上に持っていきたいので,半分の幅wと高さhをx,yから引く
_w,_h=rect[1]
#print(h,w)
x -= _h/2
y -= _w/2
x,y=int(x),int(y)
source_img = Image.open('img/pisa_rect_crop.jpg')
canvas_img = Image.open('img/pisa_rect_inpaint.jpg')
#inpaintした画像にトリムしたピサをペースト
canvas_img.paste(source_img, (x,y))
#画像を保存
canvas_img.save('img/vertical_pisa.jpg')
いざ実行
ぴ,ぴ,ピサが立った!!!
乱立させてみる
さいごに
補完部分が見えていて荒くなっていたり,あまりヴァーティカルってないのがありますが全体的にはうまく言った気がします
個人的には学習とか一切なし,アルゴリズムだけのinpaint関数の精度に驚きました
またpythonの動的型付けも便利すぎて絶句...正直型理解してないけど進めたところとかあるんでゆとり仕様に感服です
今回は色で領域を検出しているので次はエッジでできるようになれば自由度が広がる気がしますね
みんなも,Let' vertical!
おまけ
ヴァーティカルマイケル
これでノーマルグラビティ
参考資料
輪郭抽出
https://hk29.hatenablog.jp/entry/2020/02/01/162533
白色抽出
https://temari.co.jp/blog/2017/11/13/opencv-4/
トリミング
https://teratail.com/questions/219340
inpaint関数
https://lp-tech.net/articles/kb4bO/view?page=2
Pillowライブラリ
https://water2litter.net/rum/post/python_pil_paste/