Python独習!

習得したPython知識をペイフォワード

Pythonでマウスで線を引いて画像からラインプロファイルを抽出する

市販のHalocn、フリーならImageJなどで、画像からラインプロファイルを抽出することができる。ラインプロファイルを解析するところまで1個のソフトでやりたかったのでPythonで作ってみた。今回はラインプロファイルを表示するところまで。

結果

左はサイン波形でグラデーションになるように作った画像。黒線はマウスで引いた線で、リアルタイムに追従するのでどこに線を引いているかわかる。
右は引いた線に対応する輝度値をプロットしたもの。
f:id:greenhornprofessional:20200125194412p:plain

プログラム

Python 3.8.1 opencv-python 4.1.2

opencv 4系ではlineiteratorがないので、stackoverflowにあったコード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から見ている。
/* -----codeの行番号----- */