Pythonでマウスで線を引いて画像からラインプロファイルを抽出する
市販のHalocn、フリーならImageJなどで、画像からラインプロファイルを抽出することができる。ラインプロファイルを解析するところまで1個のソフトでやりたかったのでPythonで作ってみた。今回はラインプロファイルを表示するところまで。
結果
左はサイン波形でグラデーションになるように作った画像。黒線はマウスで引いた線で、リアルタイムに追従するのでどこに線を引いているかわかる。
右は引いた線に対応する輝度値をプロットしたもの。
プログラム
Python 3.8.1
opencv-python 4.1.2
opencv 4系ではlineiterator
がないので、def bresenham_march
を使わせてもらった。1か所だけ変更している。詳細はリンク参照。
import numpy as np import cv2 import math from matplotlib import pylab as plt #grobal変数の宣言 drawing = False comp_roi = False sx, sy, ex, ey = -1, -1, 0, 0 #ブレゼンハムアルゴリズム。指定した2点がつくる線分を構成する座標と輝度値を返す。 def bresenham_march(img, p1, p2): x1 = p1[0] y1 = p1[1] x2 = p2[0] y2 = p2[1] #tests if any coordinate is outside the image if ( x1 >= img.shape[0] or x2 >= img.shape[0] or y1 >= img.shape[1] or y2 >= img.shape[1] ): #tests if line is in image, necessary because some part of the line must be inside, it respects the case that the two points are outside if not cv2.clipLine((0, 0, *img.shape), p1, p2): print("not in region") return steep = math.fabs(y2 - y1) > math.fabs(x2 - x1) if steep: x1, y1 = y1, x1 x2, y2 = y2, x2 # takes left to right also_steep = x1 > x2 if also_steep: x1, x2 = x2, x1 y1, y2 = y2, y1 dx = x2 - x1 dy = math.fabs(y2 - y1) error = 0.0 delta_error = 0.0 # Default if dx is zero if dx != 0: delta_error = math.fabs(dy / dx) y_step = 1 if y1 < y2 else -1 y = y1 ret = [] for x in range(x1, x2): p = (y, x) if steep else (x, y) if p[0] < img.shape[0] and p[1] < img.shape[1]: #ret.append((p, img[p])) ret.append([p[0], p[1], img[p[0]][p[1]]]) #この1文だけオリジナルから変更している error += delta_error if error >= 0.5: y += y_step error -= 1 if also_steep: # because we took the left to right instead ret.reverse() return ret #マウスイベントから座標を返す def draw_line(event, x, y, flags, param): global sx, sy, ex, ey, drawing, comp_roi if event == cv2.EVENT_LBUTTONDOWN: #左クリック押下時 drawing = True sx = x #始点x sy = y #始点y ex = x #終点x …ライン描画されてしまうので、隠すために始点と同じ値にする ey = y #終点y …ライン描画されてしまうので、隠すために始点と同じ値にする print("Left-clicked. Start point:(%d, %d)" %(sx, sy)) elif event == cv2.EVENT_MOUSEMOVE: #左クリックでドラッグ中 if drawing == True: ex = x ey = y #print ("Drawing...") #デバッグ用。これがあるとライン描画が遅くなる else: pass elif event == cv2.EVENT_LBUTTONUP: #左クリック開放時 drawing = False print("Left-releasec. End point:(%d, %d)" %(ex, ey)) comp_roi = True img = cv2.imread('SinWave_512_512_1.jpg', cv2.IMREAD_GRAYSCALE) temp = img.copy() cv2.namedWindow('image') cv2.setMouseCallback('image', draw_line) #ループ処理。drawingとcomp_roiの状態変化を監視している。 while(1): cv2.imshow('image',temp) #左クリックドラッグ中のライン描画をリアルタイムで描画する if drawing: #if(drawing == True): の省略系 temp = img.copy() #これがないと描画が更新されない cv2.line(temp,(sx,sy),(ex,ey),(0,255,0),1) #ライン描画 #ライン描画が完了したらラインプロファイルを表示する if comp_roi: comp_roi = False P1 = (sy, sx) P2 = (ey, ex) v = bresenham_march(img, P1, P2) #ブレゼンハムアルゴリズムに始点と終点を渡す f = [] t = np.arange(len(v)) #ラインプロファイルの横軸。!要改善! for i in range(len(v)): f.append(v[i][2]) #配列vから輝度値だけを配列fに入れる plt.plot(t, f) plt.show() k = cv2.waitKey(1) & 0xFF if k == 27: #Escボタンがクリックされたらループを終了する break cv2.destroyAllWindows()
[10_LineProfile_001.py]
課題
- ラインプロファイルの横軸が0から開始になってる。始点終点の情報を使うか。これは簡単に対応可。
- 引いた線に対して垂直方向から見たラインプロファイル表示になっていない。XかYから見ている。