Bye Bye Moore

PoCソルジャーな零細事業主が作業メモを残すブログ

OpenCVで物体の角度検知を行う

オブジェクトの傾きからパラメーターをとる必要がでてきました。
できればnoOpenCVチャレンジを継続したかったのですが、時間的にそうも言っていられず……
久々にOpenCVを弄る事に。

実際のところ

考え方

  • 画像を二値化する
  • オブジェクトの領域を区切る
  • オブジェクト毎に主成分分析(PCA:principal component analysis)にて方向を判定

スクリプト

コンセプトを実行するPythonスクリプトはこんな感じに

import cv2
import numpy as np
from math import atan2, cos, sin, sqrt, pi


## 角度を設定するメソッド
def getOrientation(pts, img):
    sz = len(pts)
    data_pts = np.empty((sz, 2), dtype=np.float64)
    for i in range(data_pts.shape[0]):
        data_pts[i,0] = pts[i,0,0]
        data_pts[i,1] = pts[i,0,1]

    # 主成分分析(PCA)にて方向情報を絞る
    mean = np.empty((0))
    mean, eigenvectors, eigenvalues = cv2.PCACompute2(data_pts, mean)
  # 先ほどのベクトル情報をもとに角度を計算
    angle = atan2(eigenvectors[0,1], eigenvectors[0,0])
    print("  Rotation Angle: " + str(int(np.rad2deg(angle))) + " degrees")

    return angle


if __name__ == '__main__':
    # 画像の読み込み。第二引数"1"はグレースケール
    img_src = cv2.imread("testImage.png", 1)

    # グレースケールに変換
    img_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)

    # 二値変換
    thresh = 100
    max_pixel = 255
    ret, img_dst = cv2.threshold(img_gray,
                                 thresh,
                                 max_pixel,
                                 cv2.THRESH_BINARY)

    # 輪郭を決定
    contours, _ = cv2.findContours(img_dst, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)

    for i, c in enumerate(contours):
      # つかう領域を設定
      area = cv2.contourArea(c)
      # 大きすぎるのと小さすぎるのは無視
      if area < 1e2 or 1e5 < area:
        continue

      # 角度検知と出力
      getOrientation(c, img_src)


    # 表示
    cv2.imshow("Show BINARIZATION Image", img_dst)
    cv2.waitKey()
    cv2.destroyAllWindows()

使った画像

ペイント3Dで直線を3本引いてみました。
Shift押しながらなので45度刻み……のはず。
f:id:shuzo_kino:20220303231404p:plain

実行

実行すると、角度が出た後に画面が表示されます。

$ DISPLAY=:0.0 python3 graypython.py
  Rotation Angle: 134 degrees
  Rotation Angle: 0 degrees
  Rotation Angle: 45 degrees

ペイント3Dで引いたのでかなり正確かと思ってましたが、1度ほどのズレはどうしても出るみたいですね。