🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
OpenCV

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

Python

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

Q&A

解決済

3回答

2155閲覧

金属の部品をOpenCVでハフ変化する

holstein

総合スコア8

OpenCV

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

Python

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

0グッド

1クリップ

投稿2021/01/11 05:17

編集2021/01/16 07:23

次の画像を入力画像として金属の部品をハフ変換で輪郭や溝のエッジを抽出したいと思っていますが、パラメータの調整が難しく上手くできません。どうしればよろしいでしょうか
みなさま、間際らしい質問をしてしまい申し訳ございません。私はハフ変換で金属の部品の溝や輪郭の座標を求めたいと思っておりました。jbpb0様から意見で確率的ハフ変換を導入しても綺麗に溝や輪郭の検出ができせん。すいませんがどうすればよいか教えてくれないでしょうか。また入力画像、ハフ変換と確率的ハフ変換の出力画像とソースコードを上げておきます。
入力画像
ハフ変換の出力画像]
確率的ハフ変換の出力画像

python

1import cv2 2import numpy as np 3img=cv2.imread('input.jpg') 4gray=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)[:,:,1] 5edges = cv2.Canny(gray,255,0,apertureSize = 3) 6lines = cv2.HoughLines(edges,1,np.pi/180,200) 7print(len(lines)) 8for line in lines: 9 for rho,theta in line: 10 a = np.cos(theta) 11 b = np.sin(theta) 12 x0 = a*rho 13 y0 = b*rho 14 x1 = int(x0 + 1000*(-b)) 15 y1 = int(y0 + 1000*(a)) 16 x2 = int(x0 - 1000*(-b)) 17 y2 = int(y0 - 1000*(a)) 18 cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2) 19 20cv2.imwrite('output.jpg',img)

Python

1import cv2 2import numpy as np 3img = cv2.imread('input.png') 4gray=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)[:,:,1] 5edges = cv2.Canny(gray,50,150,apertureSize = 3) 6minLineLength = 50 7maxLineGap = 10 8lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap) 9for line in lines: 10 for x1,y1,x2,y2 in line: 11 cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2) 12 13 14cv2.imwrite('output.png',img)

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

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

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

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

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

jbpb0

2021/01/11 06:06 編集

コードの1000(4ヶ所)を、画像の縦横サイズよりも大きな数字に変えたら、画像全体に途切れず線が書かれるはず 下記の「■結果」の画像のように https://www.hello-python.com/2018/02/27/opencv%E3%82%92%E4%BD%BF%E3%81%84%E3%83%8F%E3%83%95%E5%A4%89%E6%8F%9B%E3%81%A7%E7%94%BB%E5%83%8F%E3%81%8B%E3%82%89%E7%9B%B4%E7%B7%9A%E3%82%92%E6%8E%A2%E3%81%99/ 【追記】上記ページのコードも「1000」になってますが、そこは気にしないで、処理結果画像だけ見てください
yuki23

2021/01/12 05:38

「上手くできません」とは、何をどうしてどういう結果がほしいんでしょうか?
holstein

2021/01/16 07:12

私の説明不足で申し訳ございません。最終的には金属部品の溝や輪郭の座標を求めたいと思っております。あと、質問文の方も間際らしいので書き直しておきます。
fana

2021/01/18 01:30

結局,何が得たいのかがアバウトなんですよね. 「溝や輪郭の座標」って言われても,最終的にどんなデータになるべきなのか? ってのが. ・輪郭の座標って何?  ・「輪郭」とは? 画像上での青いやつと背景との境界のことでいいの?  ・座標って? 大量の画素群の座標が欲しいの? それとも何かしら近似した形状で表現するの? ・溝の座標って何? どうやって溝の情報を表現すればいいの? ・欲しい精度はどの程度なの? …で,そういった最終目標に向かうために,今現在 ハフ変換 を用いている理由(というか,どのような考えでそれを行っているのか? という経緯)に関する説明があるべき.
fana

2021/01/18 01:37 編集

(…という諸々がはっきりしてないから,回答も若干迷走しているように見える.  ハフ変換の結果を 主観的に何となく何かしらの意味で良さそうな雰囲気 にしてみたところで,それはゴールに向かっているのか? 何がゴールなのか?) 何かよくわからんが「直線(or線分)がどこぞにありそう」となった後で,何をどうするつもりなのだろう? 各線が{輪郭,溝,それ以外}のいずれであるのかをどうやって判定するつもりなの?
guest

回答3

0

パラメータの調整が難しく上手くできません

(前提として「ハフ変換」を用いるのがmustなのだとして)
OpenCVのHoughLines等を使っていてもうまくいかないのであれば,ハフ変換を自前で実装することを視野に入れてみてはいかがでしょうか.

理由:
OpenCVの関数を使っている限り,投票空間への投票処理や,投票結果の非最大値抑制といったハフ変換のコアな(?)部分の処理に関して工夫を施すことができないので,結果に満足いかない場合にやれることが限られてしまいます.
引数になっている少数の調整パラメータだけではどうにもならない状態なのであれば,「そのケースではできあいの関数を使っていてもうまくいかない」が結論かもしれないからです.


(自前でやるにしても,HoughLinesあたりを使うにしても)
私ならとりあえず Cannyフィルタの結果を一括で丸ごとハフ変換に突っ込むようなことはしません.

エッジを勾配方向毎に分けて扱ってみたら,少しは扱いやすい(=パラメータ調整作業がしやすい)結果が得られたりしませんか?
異なる方向のエッジ点が混在しなければ偽の線が抑制されるでしょうし,
各ハフ変換ごとに異なるパラメタ値を使うことができる形になりますから調整自由度は増すでしょう.

投稿2021/01/18 02:19

編集2021/01/18 02:41
fana

総合スコア11990

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

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

0

ベストアンサー

cv2.HoughLinesP()のパラメータをいじると、背景から直線を抽出してしまうことがあるため、対象物の色が濃いことに加え、青っぽいこともマスク条件に入れました

python

1import cv2 2import numpy as np 3 4img = cv2.imread('input.png') 5iw = img.shape[1] 6img_HSV = cv2.cvtColor(img,cv2.COLOR_BGR2HSV) 7Hue = img_HSV[:,:,0] 8Sat = img_HSV[:,:,1] 9retB1,threshB1 = cv2.threshold(Hue,90,255,cv2.THRESH_BINARY) 10retB2,threshB2 = cv2.threshold(Hue,150,255,cv2.THRESH_BINARY_INV) 11retS,threshS = cv2.threshold(Sat,100,255,cv2.THRESH_BINARY) 12edges = cv2.Canny(Sat,50,150,apertureSize=3) 13edges2 = edges * threshB1 * threshB2 * threshS * 255 14lines = cv2.HoughLinesP(edges2,rho=1,theta=np.pi/360/10,threshold=int(iw*0.05),minLineLength=int(iw*0.25),maxLineGap=int(iw*0.4)) 15img_line = img.copy() 16for line in lines: 17 x1,y1,x2,y2 = line[0] 18 cv2.line(img_line,(x1,y1),(x2,y2),(0,0,255),2) 19 20cv2.imwrite('output.png',img_line) 21#cv2.imshow("output", img_line) 22#cv2.waitKey(0) 23#cv2.destroyAllWindows()

各関数のパラメータは、そんなに追い込んでませんので、いろいろ変えてみたら、もっとうまく抽出できるかも

【追記】cv2.cvtColor()の実行を1回だけにしました

投稿2021/01/16 07:31

編集2021/01/17 14:20
jbpb0

総合スコア7653

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

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

holstein

2021/01/17 03:06

教えて頂きありがとうございます。 私の勉強不足で申し訳ございませんがいくつかお尋ねなんですが、img.shapeでの[1]やHSV変換の際に行われる[:,:,0]、[:,:,1]、の意味、edges2でのマスク処理 がどのように処理が行われているのか教えてくれないでしょうか?
jbpb0

2021/01/17 07:48

> img.shapeでの[1] print(img.shape) を実行すると分かりますが、[1]は画像の横方向画素数です (縦方向画素数は[0]) これを使った理由は、私がこの質問ページからダウンロードした画像の画素数が、本当の画素数と違うかもしれない、と思ったからです
jbpb0

2021/01/17 14:22 編集

> HSV変換の際に行われる[:,:,0]、[:,:,1] [:,:,0]や[:,:,1]を付けない場合は、H, S, Vが全部揃ったデータになりますよね [:,:,0]を付けたらHだけ、[:,:,1]を付けたらSだけになります 変換は[:,:,*]を付けずに1回だけやって、変換後にH, S, Vに分離した方が、計算コスト的によかったですね 後で回答のコードを直します 【追記】直しました
jbpb0

2021/01/17 08:23

> edges2でのマスク処理 HSVのHが90〜150を「青っぽい」として、その画素は1、それ以外の画素は0になるように、threshB1とthreshB2の合わせ技でやってます HSVのSが100以上を「色が濃い」として、その画素は1、それ以外の画素は0としてるのが、threshSです したがって、 threshB1 * threshB2 * threshS は、「青っぽくて色が濃い」画素は1、そうではない画素は0になり、質問の画像では対象物がある画素が1、それ以外の画素は0に(だいたい)なります それをcv2.Canny()の結果のedgesに掛け算して、対象物の輪郭情報以外を消してます
fana

2021/01/17 08:41

inRange あたりを使うと短く書けそう.
jbpb0

2021/01/17 09:20

HSVの3次元データに範囲指定して、一発で抽出できそうな気もします
jbpb0

2021/01/17 14:28

回答に書いたコードの各関数のパラメータでは、対象物の左右端の短い辺は、抽出できません それが抽出できるように短い直線も抽出可にすると、何も無いところにも短い直線が現れてしまったからです パラメータをいろいろ試して追い込んだわけではないので、偽直線が現れないけれども左右端の短い辺は抽出できる条件があるかもしれませんが、ちょっと試したところでは見つけられませんでした 過去の質問で、輪郭の頂点座標は算出できるようになっているので、それらを順番につなぐ直線の中に左右端の短い辺もありますから、もしそれも必要なら、そこから補ってください
jbpb0

2021/01/18 01:28

ハフ変換で得られた直線の始点・終点を対象物の端に合わせる必要が無いのであれば、私が回答で書いたコードのcv2.HoughLinesP()をcv2.HoughLines()に変えて、cv2.HoughLines()とcv2.Canny()のパラメータをチューニングする方が、やりやすいかもしれません その場合でも、edges2で対象物以外はマスクしてしまう方が、背景から直線抽出がされないように悩む必要がなくなるので、チューニングし易くなると思います
holstein

2021/01/18 12:28

分かりやすく説明をしていただきありがとうございます
guest

0

python

1lines = cv2.HoughLines(edges,1,np.pi/180,150) 2print(len(lines)) 3 4# Height, Width, and channel 5h,w,c=img.shape 6 7for line in lines: 8 for rho,theta in line: 9 a = np.cos(theta) 10 b = np.sin(theta) 11 x0 = a*rho 12 y0 = b*rho 13 x1 = int(x0 + 2000*(-b)) 14 y1 = int(y0 + 2000*(a)) 15 x2 = int(x0 - 2000*(-b)) 16 y2 = int(y0 - 2000*(a)) 17 cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)

jbpb0さんのおっしゃるように、1000のところを大きくすると画像の端から端まで直線が描かれます。cv2.lineで描く線分の始点と終点が決められるので、その点が画像の内部になっていないか確認すればよいかと思います。また、パラメータの調整とのことでしたが、ここのコードで、150のところは、直線とみなされるのに必要な最低限の投票数を意味するしきい値なのでこの値を小さくするとより多くの直線を検出できます。この設定で行うと添付の画像のような結果になりました。溝のところなどは少し多く検出されているようにも感じます。役に立てば幸いです。イメージ説明

投稿2021/01/11 08:50

Kenta_py

総合スコア132

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

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

fana

2021/01/12 01:30

素朴な疑問なのですが…… お二方共に「画像の端から端まで直線を描く」話をされていますが,質問文にはそのようにしたい(あるいは,できなくて困っている)といったような記述が見えません. 線を長く描画する方法を記す 意味/理由 は何なのでしょうか?
jbpb0

2021/01/16 07:20 編集

cv2.HoughLines()をチョイスしてるからです そのチョイスは間違いで、cv2.HoughLinesP()を使うべきなんじゃないか、って言う示唆もしてます
fana

2021/01/17 06:21

得られたパラメータの可視化という意味では質問に貼られている描画結果でも十分と思える → わざわざ画像の端から端までの描画を行わせることには何の意味も無いように思える.
jbpb0

2021/01/17 06:44

元々の質問では、線が途中で途切れた画像だけがあり、「うまくできません」と書かれてました コードも、cv2.HoughLines()の方しかありませんでした それだけでは「何が」うまくできてないのか分からなかったので、二つの可能性を考えて、それぞれに対する対策を書きました ・線が対象物の途中で途切れている ・線の始点と終点が対象物の両端と違う 上記のどちらが意図通りか分からなかったので、「質問への追記・修正依頼」に書きました
fana

2021/01/17 07:01 編集

元の質問状態の時点で,私は > 「うまくできません」 の意味を,「本数が足りない」方向の意味合いかと捉えました. さすがに質問者とて,自分自身で *1000 とかいうてきとーな決め方で描いてみただけの線を「最終的な検出処理結果座標群」とは考えていないでしょう. ハフ変換の話をしていて > パラメータの調整が難しく上手くできません と言っているならば,それはこの回答の後半のような話であろう,と.
jbpb0

2021/01/17 07:15

> 自分自身で *1000 とかいうてきとーな決め方 1000はOpenCVのドキュメントのcv2.HoughLines()の例にある数字で、それをそのまま使ってるコードがネット検索すると山ほど見つかります https://docs.opencv.org/master/d6/d10/tutorial_py_houghlines.html それを確認して、質問者さんは数値を吟味せず1000をそのまま使って、結果画像を見てうまくいってないと思っているのかもしれない、って思ったのです(あくまでも、可能性の一つとして)
fana

2021/01/17 07:34 編集

そこまで極端に何も把握できていないことを想定されての話だということはわかりました. (可視化の目的での)線をとりあえずどこらへんまで描いてみるか,という点では, この質問者は少し前の質問で,同じ画像に対して領域範囲を得たりしてるみたいですから,その結果を用いて両端を決めてやれば見栄えは良さそうな気がします.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問