Python3とOpenCVで射影変換と特定色相の抽出を行う

  • 5
    Like
  • 0
    Comment

目標

hata.png

ドローンで撮影した上記画像を真上から撮影した画像にしたい!
更に田んぼの面積も求めたい!
というのが最終的な目標になります。

使用する技術

本記事では下記の言語の環境とライブラリのインストールが完了しているものだと仮定します。

  • Python3
  • OpenCV3
  • numpy
  • matplotlib

Python3で射影変換をする

最初に画像を真上からのものに変換していきます。
射影変換には、OpenCVのgetPerspectiveTransformメソッドとwarpPerspectiveメソッドを利用します。

import cv2
import numpy as np
import matplotlib.pyplot as plt

    # 画像を読み込む
    img = cv2.imread('読み込む画像名')

    # 画像の横と縦の長さを切り出す
    rows,cols,ch = img.shape

    # 画像の座標上から4角を切り出す
    pts1 = np.float32([[320,236],[1104,402],[12,332],[1053,640]])
    pts2 = np.float32([[0,0],[1250,0],[0,500],[1250,500]])

    # 透視変換の行列を求める
    M = cv2.getPerspectiveTransform(pts1,pts2)

    # 変換行列を用いて画像の透視変換
    rst = cv2.warpPerspective(img,M,(1250,500))
    # 透視変換後の画像を保存
    cv2.imwrite('出力する画像名',rst)

まず、コード全体が上記になります。

座標を入力

pts1 = np.float32([[左上の座標],[右上の座標],[左下の座標],[右下の座標]])

pst1に代入するのは、画像の中の座標になります。
著者は手作業で調べました。

pts2 = np.float32([[0,0],[1250,0],[0,500],[1250,500]])

出力座標

こちらが変換後の座標です。順番は、左上、右上、左下、右下でpst1と同じ順番です。

rst = cv2.warpPerspective(img,M,(1250,500))

下から2番目のコードが最終的な大きさや比率を決定しています。

元画像の比率で出力

rst = cv2.warpPerspective(img,M,(rows,cols))

元画像の比率のまま出力してくれます。

射影変換の結果

3.png

matplotlibで出力した結果が上記になります。
左の画像の赤い点が指定した座標です。

右側の画像が上から撮った角度に直したものです。

特定の色相を抽出して面積を計算

python3で特定の画像を抽出し、その色相の面積を計算します。

def extract_color (src,h_th_low,h_th_up,s_th,v_th):
    # HSV変換
    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    h,s,v = cv2.split(hsv)


    # 色相の赤における0°と360°を調整
    if h_th_low > h_th_up:
        ret,h_dst_1 = cv2.threshold(h,h_th_low,255,cv2.THRESH_BINARY)
        ret,h_dst_2 = cv2.threshold(h,h_th_up,255,cv2.THRESH_BINARY_INV)

        # BINARYとBINARY_INVのどちらかをとる
        dst = cv2.bitwise_or(h_dst_1,h_dst_2)

    else:
        # 赤以外はそのまま適用する
        ret,dst = cv2.threshold(h,h_th_low,255,cv2.THRESH_TOZERO)
        ret,dst = cv2.threshold(dst,h_th_up,255,cv2.THRESH_TOZERO_INV)

        ret,dst = cv2.threshold(dst,0,255,cv2.THRESH_BINARY)

        # S(明度)とV(彩度)についても同様の場合分けを行う
        ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
        ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)

        dst = cv2.bitwise_and(dst, s_dst)
        dst = cv2.bitwise_and(dst, v_dst)

        return dst

if __name__ == '__main__':
    # 生成された画像を読み込み
    right_image = cv2.imread('読み込むファイル名')

    # 緑色だけを抽出
    green_image = extract_color(right_image,0,178,50,0)

    # 緑色を抽出後の画像を読み込み
    m = cv2.countNonZero(green_image)
    h, w = green_image.shape

    # 画像の面積と緑色の割合を計算
    per = round(100*float(m)/(w * h),1)

    # 計算結果を表示
    print("Moment[px]:",m)
    print("Percent[%]:", per)

    # 最終的な画像を表示、保存
    cv2.imshow("Mask",green_image)
    cv2.imwrite('出力するファイル名',green_image)
    cv2.waitKey(0)

    # 全てのウィンドウを閉じる
    cv2.destroyAllWindows()

出力画像

agri.png

先ほどのOutputの画像から緑色の色相を抽出しました。

計算結果

Moment[px]:522283
Percent[%]:83.6

全体の中の83.6%が緑色だよと教えてくれます。

全てのコード(サンプル)

下記がコード全てになります。

import cv2
import numpy as np
import matplotlib.pyplot as plt

def fix_distortion():
    # 画像を読み込む
    img = cv2.imread('./sources/hata.png')

    # 画像の横と縦の長さを切り出す
    rows,cols,ch = img.shape

    # 画像の座標上から4角を切り出す
    pts1 = np.float32([[320,236],[1104,402],[12,332],[1053,640]])
    pts2 = np.float32([[0,0],[1250,0],[0,500],[1250,500]])

    # 透視変換の行列を求める
    M = cv2.getPerspectiveTransform(pts1,pts2)

    # 変換行列を用いて画像の透視変換
    rst = cv2.warpPerspective(img,M,(1250,500))
    # 透視変換後の画像を保存
    cv2.imwrite('./sources/yugami.png',rst)


def extract_color (src,h_th_low,h_th_up,s_th,v_th):
    # HSV変換
    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    h,s,v = cv2.split(hsv)


    # 色相の赤における0°と360°を調整
    if h_th_low > h_th_up:
        ret,h_dst_1 = cv2.threshold(h,h_th_low,255,cv2.THRESH_BINARY)
        ret,h_dst_2 = cv2.threshold(h,h_th_up,255,cv2.THRESH_BINARY_INV)

        # BINARYとBINARY_INVのどちらかをとる
        dst = cv2.bitwise_or(h_dst_1,h_dst_2)

    else:
        # 赤以外はそのまま適用する
        ret,dst = cv2.threshold(h,h_th_low,255,cv2.THRESH_TOZERO)
        ret,dst = cv2.threshold(dst,h_th_up,255,cv2.THRESH_TOZERO_INV)

        ret,dst = cv2.threshold(dst,0,255,cv2.THRESH_BINARY)

        # S(明度)とV(彩度)についても同様の場合分けを行う
        ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
        ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)

        dst = cv2.bitwise_and(dst, s_dst)
        dst = cv2.bitwise_and(dst, v_dst)

        return dst


if __name__ == '__main__':
    # fix_distortionメソッド実行
    fix_distortion()

    # 生成された画像を読み込み
    right_image = cv2.imread('./sources/yugami.png')

    # 緑色だけを抽出
    green_image = extract_color(right_image,0,178,50,0)

    # 緑色を抽出後の画像を読み込み
    m = cv2.countNonZero(green_image)
    h, w = green_image.shape

    # 画像の面積と緑色の割合を計算
    per = round(100*float(m)/(w * h),1)

    # 計算結果を表示
    print("Moment[px]:",m)
    print("Percent[%]:", per)

    # 最終的な画像を表示、保存
    cv2.imshow("Mask",green_image)
    cv2.imwrite('./sources/agri.png',green_image)
    cv2.waitKey(0)

    # 全てのウィンドウを閉じる
    cv2.destroyAllWindows()

参考になれば嬉しいです。