画像処理で水平線などを処理する方法はいろいろあるかと思いますが、
pythonでOpenCVモルフォロジー変換を使って水平線処理などを考えてみます。
モルフォロジー変換は一般的には膨張、収縮処理のような処理を指しますが
これに極端なカーネル(構造要素)を指定してやってみます。一種のトリックです。
処理について
下記のように画像処理を行います。
1.二値化を行う
2.(n x 1)カーネル(構造要素)を生成
水平線処理用は(50x1), (100x1) ... こんな感じで作っていく
垂直線処理用は(1x8), (1x10) ... こんな感じで作っていく
3. 生成したカーネルでエロージョン
4. 生成したカーネルでディレージョン
カーネル(構造要素)について
カーネル(構造要素)は下記のように生成する。
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(20,1))
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],
dtype=uint8)
>>> cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
array([[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1]], dtype=uint8)
ちなみに一般的な膨張・収縮も載せておきます。
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=uint8)
モルフォロジー変換について
horizontal = cv2.erode(horizontal, hKernel)
horizontal = cv2.dilate(horizontal, hKernel)
エロージョン実行後にディレージョンを行う形とした。
入力画像
横線がどのように処理されるかを見ます。
プログラム
import numpy as np
import cv2
import sys
# イメージ表示
def showImage(winname, img):
cv2.imshow(winname, img)
cv2.moveWindow(winname, 500, 0)
cv2.waitKey(0)
cv2.destroyWindow(winname)
def main(imagefile):
# imageロード
src = cv2.imread(imagefile,cv2.IMREAD_GRAYSCALE)
if src is None:
print ('Error image: ' + imagefile)
return -1
# 反転して2値化
gray = cv2.bitwise_not(src)
binimage = cv2.adaptiveThreshold(gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 15, -2)
# 2値化表示
showImage("BINARIZE", binimage)
for hv in range(50, 301, 50):
morphHorizontalLine(binimage, hv)
for vv in range(2, 17, 2):
morphVerticalLine(binimage, vv)
return 0
# モルフォロジー変換 水平線を抽出
# hor_size 水平線を抽出
# モルフォロジー変換構造要素パラメータ
def morphHorizontalLine(binimage, hor_size):
horizontal = np.copy(binimage)
# モルフォロジー演算により水平線を
# 抽出するための構造要素を生成.
hKernel = cv2.getStructuringElement(cv2.MORPH_RECT,
(hor_size, 1))
# モルフォロジー演算により水平線を抽出
horizontal = cv2.erode(horizontal, hKernel)
horizontal = cv2.dilate(horizontal, hKernel)
cap = 'hor_size=%d' % (hor_size)
cv2.putText(horizontal, cap, (10,25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255), 1,cv2.LINE_AA)
# 水平線画像表示
showImage("HORIZONTAL LINES", horizontal)
# モルフォロジー変換 垂直線を抽出
# ver_size 垂直線を抽出
# モルフォロジー変換構造要素パラメータ
def morphVerticalLine(binimage, ver_size):
vertical = np.copy(binimage)
# モルフォロジー演算により垂直線を
# 抽出するための構造要素を生成.
hKernel = cv2.getStructuringElement(cv2.MORPH_RECT,
(1,ver_size))
# モルフォロジー演算により水平線を抽出
vertical = cv2.erode(vertical, hKernel)
vertical = cv2.dilate(vertical, hKernel)
cap = 'ver_size=%d' % (ver_size)
cv2.putText(vertical, cap, (10,25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255), 1,cv2.LINE_AA)
# 水平線画像表示
showImage("VERTICAL LINES", vertical)
if __name__ == "__main__":
main('image/Delta.png')
開発環境
Windows10 Anaconda
python 3.7.6
OpenCV 3.4.1
実行結果
水平線抽出
hor_size=50は(50,1)カーネル(構造要素)で処理を行っています。
hor_size=100は(100,1),(300,1)まで実行。
縦線と上部の短い横線が消えて行ってます。
垂直線抽出
ver_size=2は(1,2)カーネル(構造要素)で処理を行っています。
ver_size=6は(1,6),(1,8)と実行します。
横線は消えています。いいかんじ。
応用
モルフォロジー変換の動きが分かったところで、応用として楽譜で五線を
処理をしてみます。ちなみに楽譜は景気の良さそうな「軍艦マーチ」。
先頭を抜粋しました。
応用編 プログラム
import numpy as np
import cv2
import sys
# イメージ表示
def showImage(winname, img):
cv2.imshow(winname, img)
cv2.moveWindow(winname, 500, 0)
cv2.waitKey(0)
cv2.destroyWindow(winname)
def main(imagefile):
# imageロード
src = cv2.imread(imagefile, cv2.IMREAD_GRAYSCALE)
if src is None:
print ('Error image: ' + imagefile)
return -1
# 反転して2値化
gray = cv2.bitwise_not(src)
binimage = cv2.adaptiveThreshold(gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -2)
# 2値化表示
showImage("BINARIZE", binimage)
morphLine(binimage, 5)
return 0
# モルフォロジー変換
# ver_size 水平線を抽出 モルフォロジー変換構造要素パラメータ
def morphLine(binimage, ver_size):
# 水平線の抽出に画像バッファ生成
vertical = np.copy(binimage)
# モルフォロジー演算により垂直線を抽出するための構造要素を生成.
vKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, ver_size))
# モルフォロジー演算により垂直線を抽出
vertical = cv2.erode(vertical, vKernel)
vertical = cv2.dilate(vertical, vKernel)
# 垂直線画像を表示
showImage("VERTICAL LINES", vertical)
# 1) エッジを抽出
# 2) 膨張 dilate(contour_edge)
# 3) 平滑化 smooth
# 4) 垂直線画像とエッジをマージ
# 輪郭エッジを抽出し、画像を滑らかにする
contour_edge = cv2.adaptiveThreshold(vertical, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, -2)
showImage("CONTOUR EDGE", contour_edge)
# エッジ画像の膨張
kernel = np.ones((2, 2), np.uint8)
contour_edge = cv2.dilate(contour_edge, kernel)
showImage("DILATE contour_edge", contour_edge)
# 画像のぼかし (平滑化)
# 平均を取るには正規化された箱型フィルタ
smooth = cv2.blur(np.copy(vertical), (2, 2))
# 垂直線画像とエッジをマージ
# エッジが存在する部分について座標抽出
# 垂直画像に抽出座標よりエッジから上書き
(height, width) = np.where(contour_edge != 0)
vertical[height, width] = smooth[height, width]
# 結果画像表示
showImage("RESULT IMAGE", vertical)
if __name__ == "__main__":
main('image/Warship_march_score.png')
5x1カーネル(構造要素)で実行しています。
処理後ろ側は、画像の体裁をとるための復元処理で
特に本質ではないので説明は省略します。
応用編 実行結果
こんなところでしょうか。
カーネル(構造要素)を変えることで「こんなことまでできるよ」と言うところについて、
内容的には大したことはないのだけれど、それで出来てしまうことがあることに
感じる物があり書いて見ました。
参考
モルフォロジー変換 — OpenCV-Python Tutorials 1 documentation
opencv.org - Extract horizontal and vertical lines by using morphological operations
海上自衛隊東京音楽隊 JMSDF BAND, TOKYO - 行進曲「軍艦」