質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

1491閲覧

python openCV 等間隔な輪郭線の抽出

hamutarosan

総合スコア1

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2022/12/12 18:39

前提

python独学で学習し始めの初心者です。
openCVを用いて輪郭の大まかな抽出(等間隔かつ特徴点を拾う)方法を模索しています。
現時点では輪郭の近似が終わり、そのうえでの点間の距離が等間隔とは程遠いため、考えている方法として、各点間の距離を測り一番距離が短いものの決定、かつその距離を基準に点間の距離が長い部分に点を置きたいと思っているのですが可能であるのか、もし可能ならば、考え方を教えていただきたいです。

実現したいこと

  • 輪郭の近似をしつつ点間の距離をほぼ等しくしたい

該当のソースコード

python

1import cv2 2import numpy as np 3from IPython import display 4from matplotlib import pyplot as plt 5from matplotlib import patches 6 7 8def imshow(img, format=".jpg", **kwargs): 9 """ndarray 配列をインラインで Notebook 上に表示する。 10 """ 11 img = cv2.imencode(format, img)[1] 12 img = display.Image(img, **kwargs) 13 display.display(img) 14 15 16def draw_contours(img, contours, ax): 17 """輪郭の点及び線を画像上に描画する。 18 """ 19 ax.imshow(img) 20 ax.set_axis_off() 21 22 for i, cnt in enumerate(contours): 23 # 形状を変更する。(NumPoints, 1, 2) -> (NumPoints, 2) 24 cnt = cnt.squeeze(axis=1) 25 # 輪郭の点同士を結ぶ線を描画する。 26 ax.add_patch(plt.Polygon(cnt, color="b", fill=None, lw=2)) 27 # 輪郭の点を描画する。 28 ax.plot(cnt[:, 0], cnt[:, 1], "ro", mew=3, ms=4) 29 # 輪郭の番号を描画する。 30 #ax.text(cnt[0][0], cnt[0][1], i, color="r", size="20", bbox=dict(fc="w")) 31 32# 画像を読み込む。 33img = cv2.imread("dog.jpg") 34converted = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) 35 36# グレースケールに変換する。 37gray = cv2.cvtColor(converted, cv2.COLOR_BGR2GRAY) 38 39#ネガポジ変換で反転 40gray2 = cv2.bitwise_not(gray) 41 42 43# 2値化する 44ret, bin_img = cv2.threshold(gray2, 150, 255, cv2.THRESH_BINARY) 45 46# 輪郭を抽出する。 47contours, hierarchy = cv2.findContours( 48 bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE 49) 50 51 52approx_contours = [] 53for i, cnt in enumerate(contours): 54 # 輪郭の周囲の長さを計算する。 55 arclen = cv2.arcLength(cnt, True) 56 # 輪郭を近似する。 57 approx_cnt = cv2.approxPolyDP(cnt, epsilon=10, closed=True) 58 approx_contours.append(approx_cnt) 59 # 元の輪郭及び近似した輪郭の点の数を表示する。 60 print(f"contour {i}: before: {len(cnt)}, after: {len(approx_cnt)}") 61 print(approx_cnt) 62 63 64 65fig, ax = plt.subplots(figsize=(8, 8)) 66draw_contours(converted, approx_contours, ax) 67plt.show()

試したこと

approx_cntで座標が取れているのでユークリッド距離を用いたら、、と思ったのですが座標をどう表すか考え方が足りず断念しました。
現状の画像はこのような感じです。
イメージ説明

補足情報(FW/ツールのバージョンなど)

バージョン:python3.10

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

insecticide

2022/12/12 20:11

面白い質問ですね。 逆質問で申し訳ございませんが、 気になる事として😺輪郭上の『特徴点』がどうやって取得したのでしょうか。 人間の目からすれば、ローカル的な画像特徴がまったく同じなのに、『特徴点』として選ばれたり、選ばれなかったりしていますね。
PondVillege

2022/12/13 03:43

findContoursのオプションでcv2.CHAIN_APPROX_SIMPLEではなくcv2.CHAIN_APPROX_NONEにしてから,等間隔になるよう枝切りをするのは考えたりしてますでしょうか
hamutarosan

2022/12/13 04:47

ps_aux_grep様 その発想は思いつきませんでした。 この場合特徴点も取り入れての等間隔抽出は可能なのですか?
guest

回答2

0

点間の距離が長い部分に点を置きたいと思っている

その置くべき点とはどこになるのか? というのが問題であるということですよね.

cv2.approxPolyDP(cnt, epsilon=10, closed=True)

と同じことを,epsilon の値を小さくして実施した結果というのは,epsilon=10 の結果よりもより「近似していない(=点がたくさんある)」折れ線になっているハズなので,その結果から点を引っ張ってくるという手が考えられるかと思います.
epsilonの値が大きいやつと小さいやつとの「いいとこどり」をしよう,みたいな)

例えば epsilon=10 の結果上で「点間の距離が長い部分」の中点座標に最も近い epsilon=5 の結果での点を探す感じで.
(そういう点が無い場合は… より小さい epsilon での結果を参照するとか,大元の近似しない輪郭データから座標を引っ張ってくるとか)

投稿2022/12/13 02:08

編集2022/12/13 02:09
fana

総合スコア11632

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hamutarosan

2022/12/13 04:39

回答ありがとうございます。epsilonの違いを利用しての取得は考えてなかったので実行してみようと思います。
insecticide

2022/12/13 06:14

fanaさん アイディアは素晴らしいですが、それは検出された輪郭の『連続的な座標セット』をOpenCVから取得できるのを前提とするのではないでしょうか。 私の記憶ではOpenCVが輪郭の連続的な座標セットそのものを提供してくれないですけれども。。。 違うのでしょうか。
fana

2022/12/13 07:34

> 連続的な座標セット というのが,どういう意味かイマイチ掴みかねますが… 少なくとも findContours では引数指定次第で輪郭の全画素の位置を得られるので,「大元の近似しない輪郭データ」としては十分ではないかな,と思います.
fana

2022/12/13 07:37

> findContours では引数 「質問へのコメント」欄の方で少し触れられているようです. ※CHAIN_APPROX_SIMPLE を用いても情報が落ちるわけではなくデータの表現方法が変わるだけなハズなので,(ちょっと面倒でしょうけども)全画素の位置は得られるハズです.
fana

2022/12/13 07:43

他の話としては, approxPolyDP によって得られた頂点群によって元の輪郭線がいくつかの「区間」に分割された ↓ 元の輪郭線のそれら各区間について2次曲線でも当てはめてどうのこうの… みたいな方向も思いついたりしましたが,無意味に面倒すぎるからダメかな.
insecticide

2022/12/13 08:53

fana さん ご教授ありがとうございます。 >2次曲線でも当てはめてどう これも一つの手ですね。場合によって非常に有力な手法かもしれません。 2~3次ベジエ曲線, B-spline 曲線, NURBS
guest

0

ベストアンサー

openCVを用いて輪郭の大まかな抽出(等間隔かつ特徴点を拾う)方法を模索しています。

まず厳密に「等間隔」としたければ、輪郭を構成する各線分長の最大公約数が間隔長となります。
しかし線分長は実数なのでそのまま求めるととても小さな値になってしまいます。
そこで近似的に間隔長を求める(完全に等間隔になるわけではない)方向で考えました。
いったん間隔長が求まれば、あとはnp.linspaceで2点間を等間隔に分割する点群を簡単に求めることができます。
結果として以下のようなコードで実現できそうです。

なお、何をもって近似的な間隔長とするかは

  • 最短線分の長さ
  • 全体の輪郭長をN分割する長さ

などいろいろ考えられると思います。

もっとも、findContours、approxPolyDPのパラメータ次第でどうにかできそうな気もしますが。

Python

1import cv2 2import numpy as np 3from matplotlib import pyplot as plt 4from matplotlib import patches 5 6def draw(img, cnt, ax): 7 ax.imshow(img) 8 ax.set_axis_off() 9 ax.add_patch(plt.Polygon(cnt, color="b", fill=None, lw=2)) 10 ax.plot(cnt[:, 0], cnt[:, 1], "ro", mew=3, ms=4) 11 12 13fig, ax = plt.subplots() 14 15# 画像から特徴点を得る 16img = cv2.imread("test.png") 17converted = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) 18gray = cv2.cvtColor(converted, cv2.COLOR_BGR2GRAY) 19gray2 = cv2.bitwise_not(gray) 20ret, bin_img = cv2.threshold(gray2, 150, 255, cv2.THRESH_BINARY) 21contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 22 23# とりあえず1輪郭のみで 24cnt = contours[0] 25arclen = cv2.arcLength(cnt, True) 26cnt = cv2.approxPolyDP(cnt, epsilon=10, closed=True) 27cnt = cnt.squeeze(axis=1) 28 29# 特徴点のみ 30draw(converted, cnt, ax) 31plt.savefig('ret1.png') 32 33 34# 輪郭から各線分の始点と終点を得る 35pts = cnt 36pts_st = pts 37pts_ed = np.vstack( [pts[1:,:], pts[0,:]]) 38 39# 各線分長を算出 40dists = np.linalg.norm( pts_st - pts_ed, axis=1) 41 42# 分割区間(単位)長を決定 43step_len = min(dists) # とりあえず最短線分を採用 44#step_len = arclen / 50 # 全体で50分割 45 46# 各線分の分割数(四捨五入して整数に)を算出 47divs = np.round(dists / step_len).astype(int) 48 49# 各線分を分割 50line_pts = [] 51for pt_st, pt_ed, div_cnt in zip(pts_st, pts_ed, divs): 52 pts = np.linspace(pt_st, pt_ed,div_cnt, endpoint=False) 53 line_pts.append(pts) 54line_pts = np.vstack(line_pts) 55 56draw(converted, line_pts, ax) 57plt.savefig('ret2.png')

元画像
イメージ説明
分割前
イメージ説明
分割後:最短区間
イメージ説明
分割後:N分割
イメージ説明

投稿2022/12/13 04:07

can110

総合スコア38233

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hamutarosan

2022/12/13 04:31

findContours、approxPolyDPのパラメータだけに集中してしまいそれ以外に焦点を当てておりませんでしたがこのような考え方もあるのですね。ありがとうございます、ぜひ参考にさせていただます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問