この点は内側?

20201012_不思議な雲

よく輪郭や多角形にこの点があるかどうかを調べたいことがある。
通常調べる方法で、点と彼方の仮想点の線分と輪郭の線分が
何回交わるかで調べます。


OpenCVでは用意されていて、それを使わなくていいのがいいな。





点と輪郭の関係を調べる関数


retval = cv.pointPolygonTest( contour, pt, measureDist)
  • contour - 入力輪郭(点座標配列)
  • pt - 点
  • measureDist - Trueの場合、関数はポイントから最も近い輪郭エッジまでの符号付き距離を推定します。
    それ以外の場合、関数はポイントが輪郭の内側にあるかどうかのみをチェックします。


この関数は、点が輪郭の内側にあるか、外側にあるか、輪郭上にあるか
または頂点と一致するかを判別します。
それに応じて、正(内側)、負(外側)、またはゼロ(エッジ上)の値を返します。
measureDist = falseの場合、戻り値はそれぞれ+ 1、-1、および0です。
それ以外の場合、戻り値は、点と最も近い輪郭エッジの間の符号付き距離です。

これを使って、いろいろ試したいと思います。



処理


画像読み込み

データは下記画像の二値のペンタグラムを読み込み、処理します。

20200123_Pentagram.png


輪郭を抽出

20200123_bgr_image.png

cv2.findContours()を使って普通に輪郭を取ります。


輪郭からの距離イメージを作成

20200123_dw_img.jpg

画像を全スキャンして、輪郭の内側であれば赤、輪郭の外側であれば黄色、
輪郭線の上であれば白と塗分けてます。
上画像の輪郭からの距離を示すもので、色が濃いところが距離が近く、
薄くて黒っぽいほど遠いです。
輪郭からの距離をグラディーションで表現するとこんな感じになります。
ここで、cv2.pointPolygonTest()を使用します。


内接円を取得

内接円パラメータも取得できるので、ついでに内接円も描画。
20200123_dw_img2.png

距離イメージで求めた最大値,最小値の座標を使って描画します。
黒い丸がそれです。



Pythonプログラム

import cv2
import numpy as np
import math

def main():
raw_img = cv2.imread('Pentagram.png', cv2.IMREAD_GRAYSCALE)
height, width = raw_img.shape[:2]

# 二値化
_,src_img = cv2.threshold(raw_img, 0, 255, \
cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 輪郭を取得する
contours, _ = cv2.findContours(src_img, \
cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 輪郭までの距離を計算する
dist_img = np.empty(src_img.shape, dtype=np.float32)
for i in range(src_img.shape[0]):
for j in range(src_img.shape[1]):

# この関数は,点が輪郭の内側にあるか,外側にあるか,
# 輪郭上に乗っている(あるいは,頂点と一致している)かを判別。
#
# 返却値
# 正値(内側),
# 負置(外側)
# 0(辺上)
dist_img[i,j] = cv2.pointPolygonTest(contours[0], (j,i), True)

# 最小値, 最大値, 最小値の座標, 最大値の座標
minVal, maxVal, min_loc, max_loc = cv2.minMaxLoc(dist_img)
minVal = abs(minVal) # 外側
maxVal = abs(maxVal) # 内側

# 距離をグラフィカルに描く
dw_img = np.zeros((height, width, 3), dtype=np.uint8)
for y in range(src_img.shape[0]):
for x in range(src_img.shape[1]):
if dist_img[y,x] < 0:
# 輪郭の外側
v = int(255.0 - abs(dist_img[y,x]) * 255.0 / minVal)
dw_img[y,x] = (0,v,v)
elif dist_img[y,x] > 0:
# 輪郭の内側
v = 255 - dist_img[y,x] * 255 / maxVal
dw_img[y,x] = (0,0,v)
else:
# 輪郭(白)
dw_img[y,x] = (255,255,255)
cv2.imwrite('dw_img.jpg', dw_img)

# 距離と内接円
dw_img2 = np.zeros((height, width, 3), dtype=np.uint8)
dw_img2.fill(255)
cv2.drawContours(dw_img2,contours,0,(255,0,255),-1)

# 距離イメージで求めた最大値,最小値の座標
radius = int(maxVal)
cv2.circle(dw_img2,max_loc, radius, (100,0,0), 2, cv2.LINE_AA)
cv2.imwrite('dw_img2.png', dw_img2)

cv2.imshow('dw_img', dw_img)
cv2.imshow('dw_img2', dw_img2)
cv2.imshow('Source', src_img)
cv2.waitKey()

if __name__ == '__main__':
main()

こんな感じでしょうか。
機能を1個調べるだけなんだけど、うまくできるとちょっとうれしい。
いい感じ。



動作環境

Windows10 Anaconda
Python 3.8.1
OpenCV 4.0.1
numpy 1.18.1



追記




関連



参考



写真引用

スネーィルさんによる写真ACからの写真

#画像処理
#Python

関連記事
スポンサーサイト



コメント

非公開コメント