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

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

詳細はこちら
OpenCV

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

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

3416閲覧

スカスカな画像のエッジ抽出方法

yak_jun

総合スコア13

OpenCV

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

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

1クリップ

投稿2021/02/03 14:50

前提条件

例えば以下の画像のエッジを抽出する場合、OpenCVのCannyを利用する事で抽出出来ます。

python3

1img = cv2.imread("img1.png") 2gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 3canny_img = cv2.Canny(gray_img, 100,200)

img1 canny_img

実現したいこと

スカスカの画像を入力した場合に、上記内容と同じようなエッジを抽出したいです。
img2

人間の目で見ると境界線を引けそうに見えるのですが、どのような考え方で抽出すれば良いのかが思い付きません
なお、このスカスカな画像の点の間隔は、実際には一定ではありません
モルフォロジーは試したのですが、隙間が大きい箇所については膨張処理では補いきれませんでした。

用途

LIDARの点群を平面に透視投影した際の、色の境目からエッジを抽出したい
→ LIDARに映っている物の輪郭を画像として取得したい

良い方法などありましたら、ご教示いただければ幸いです。
よろしくお願いいたします。

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2021/02/03 22:14

おもしろそうですね。これの「黒ではない部分」は規則正しく並んでいるのでしょうか(○○px単位で方眼状など)。
fana

2021/02/04 01:00 編集

LIDARの点群のデータなのであれば, LIDARから見れば密(というか点同士の "隣接関係" がわかるデータ)なのでは? (なので,その方向の隣接関係を用いて並べた絵をつくれば,隙間の無い絵になるのでは)
yak_jun

2021/02/04 01:24

> これの「黒ではない部分」は規則正しく並んでいるのでしょうか(○○px単位で方眼状など)。 こちらに関しましては、並びは不定になります。 (サンプルの絵を作るのに雑なプログラムを書いた結果方眼状に並びました…) > LIDARから見れば密(というか点同士の "隣接関係" がわかるデータ)なのでは? 確かにそうとも言えるかもしれません。 この観点でも考察を進めてみようと思います。 ありがとうございます。
fana

2021/02/04 01:42

例えば,ある横方向の1ライン分のデータについて言えば, 「横方向にφ度単位でスキャンした」みたいなデータなんじゃないかな,と. であれば,「作る画像の横方向の1pixelがφ度相当」という話で絵を作れば密なものになるであろう,と. (それでも LIDAR だから,「データが無い」部分が存在し得るので,その点は別途何かしらうまく扱う必要はある)
guest

回答3

0

アイデア自体はすぐ出てましたが、書き込む時間を待っていたらもうクローズされてしまってますね…。
参考まで。


完成形
以下に掲載するコードからいらない処理を削れば、恐らく見やすさを保ちながら全てこみこみ10-20行くらいまで短くできると思います。
イメージ説明


考え方
基本的な考え方は、以下の通りです。

a. HSVならHを使って○○色っぽさの尺度で分離できる
b. ボカせば大体均せる
c. カッチリするなら3値化してやればいい

順を追って説明
0. [ダメな見本]読み込んだ画像をぼかした後にHSVで分割してエッジ検出
(hからそのままエッジ検出は筋が良くない)
イメージ説明
ここから無理やりやるなら、閾値で白っぽいところを抜いてモルフォロジー+scikit-imageでスケルトン検出とかでしょうか。

1. 読み込んだ画像をgaussianした後にHSVで分割して鮮やかに
(少し良くなりそう)
イメージ説明

2. Kmeansで三値化(画像処理で言えば減色処理、統計的にはクラスタリング)する
(だいぶいい感じ)
イメージ説明

3. hに対してLaplacianでエッジ検出
(BGRに対してエッジ検出をすると白だけでなくBやRの筋が残るのでhのみで処理が推奨です)
Cannyもいいかもしれませんね。
イメージ説明

Python3

1import numpy as np 2import cv2 3 4def kmeans_process(img): 5 ####### 6 # Kmeans 7 8 # http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html 9 Z = img_mod.reshape((-1,3)) 10 11 # convert to np.float32 12 Z = np.float32(Z) 13 14 # define criteria, number of clusters(K) and apply kmeans() 15 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) 16 K = 3 17 ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS) 18 19 # Now convert back into uint8, and make original image 20 center = np.uint8(center) 21 res = center[label.flatten()] 22 img_kmeans = res.reshape((img_mod.shape)) 23 24 cv2.imshow('Kmeans',img_kmeans) 25 return img_kmeans 26 27################################### 28# Minimal process 29################################### 30 31####### 32# Read image 33img = cv2.imread("img.png") 34# cv2.imshow("img",img) 35 36####### 37# Gaussian process 38img_mod = cv2.GaussianBlur(img, (15, 15), 3) 39# cv2.imshow("img_gauss",img_mod) 40# cv2.imwrite("img_gauss.png",img_mod.astype(np.uint8)) 41####### 42# Split it! 43h,s,v = cv2.split(cv2.cvtColor(img_mod,cv2.COLOR_BGR2HSV)) 44 45# # Not goot method 46# img_not_good = cv2.Laplacian(h,cv2.CV_64F) 47# cv2.imwrite("img_not_good.png",img_not_good.astype(np.uint8)) 48 49################################### 50# Optional process 51################################### 52####### 53# Multiply it! 54mul = 10 55s = np.clip(s*mul,0,255).astype(np.uint8) 56v = np.clip(v*mul,0,255).astype(np.uint8) 57img_mod = cv2.cvtColor(cv2.merge((h,s,v)),cv2.COLOR_HSV2BGR) 58# cv2.imshow("img_vivid",img_mod) 59# cv2.imwrite("img_vivid.png",img_mod.astype(np.uint8)) 60 61####### 62# Kmeans 63img_kmeans = kmeans_process(img_mod) 64# cv2.imshow("img_kmeans",img_kmeans) 65# cv2.imwrite("img_kmeans.png",img_kmeans.astype(np.uint8)) 66 67 68####### 69# Split it! 70h,s,v = cv2.split(cv2.cvtColor(img_kmeans,cv2.COLOR_BGR2HSV)) 71 72####### 73# Edge detection 74img_edge = cv2.Laplacian(h,cv2.CV_64F) 75 76cv2.imshow('img_edge',img_edge) 77# cv2.imwrite("img_edge.png",img_edge.astype(np.uint8)) 78cv2.waitKey(0)

投稿2021/02/06 00:05

編集2021/02/06 00:08
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

0

ベストアンサー

(「質問への追記・修正」側にて,そもそも密な絵を作れるのではないか?という疑問を投げかけておりますが,それはそれとして)

提示されたようなスカスカなデータを相手にするとしたら,原始的な話をすれば,下図のようにエッジ検出に使うカーネルを適当なでかさにして,各マスの値を求む際には,そのマスの中にある「有効な」画素値の平均等を用いればよいでしょう(つまり黒の画素は無視して色がある画素だけを扱う).

イメージ説明

↑の図に描いたカーネルはかなり極端なでかさですが,まぁ相応のサイズにすればよいかと.

で,ほんとにでかいカーネルで計算するのは処理時間とかが嫌な感じでしょうから
【カーネルをでかくする ≒ 画像を縮小する】という方向で何かしら考えてやれば良いのではないかと.
(例えば,縮小結果の画素値を決める際に,元の画像の黒画素を無視するような)


とりあえず原始的なのを何の工夫も無くやってみた.
色を見るのは面倒なので,画像はグレースケールで扱った.
画像は左から

  • グレースケールの原画
  • でかいSobel (X方向) の結果をてきとーに可視化したもの
  • 同 y方向
  • {x方向, y方向}の2つから得たエッジ強度をてきとーに可視化したもの

カーネルがでかい分だけ,結果も太いが.

イメージ説明

C++

1//作業関数.正方形領域内の,輝度値0でない画素群の輝度平均値を算出. 2double AreaAve( const cv::Mat &Src8U, int left, int top, int S ) 3{ 4 unsigned int Sum=0, n=0; 5 for( int dy=0; dy<S; ++dy ) 6 { 7 const unsigned char *p = Src8U.ptr<unsigned char>( top + dy, left ); 8 for( int dx=0; dx<S; ++dx, ++p ) 9 { if( *p ){ Sum += *p; ++n; } } 10 } 11 return ( n ? double(Sum)/n : 0 ); 12} 13 14//32bit float 画像をてきとーに表示するためのヘルパ. 15void Show32F( const std::string &WndName, cv::Mat &Img ) 16{ 17 double Min,Max; 18 cv::minMaxLoc( Img, &Min,&Max ); 19 cv::Mat ShowImg( Img.size(), CV_8U ); 20 double alpha = 255 / (Max-Min); 21 double beta = -Min * alpha; 22 Img.convertTo( ShowImg, ShowImg.type(), alpha,beta ); 23 cv::imshow( WndName, ShowImg ); 24} 25 26//main 27int main() 28{ 29 cv::Mat SrcImg = cv::imread( "Dots.png", cv::IMREAD_GRAYSCALE ); 30 if( SrcImg.empty() )return 0; 31 32 const int S = 7; //1マスのサイズ 33 const int KernelR = (S * 3) / 2; 34 const int dx[8] = { -1,0,1, -1,1, -1,0,1 }; 35 const int dy[8] = { -1,-1,-1, 0,0, 1,1,1 }; 36 const int wx[8] = { -1,0,1, -2,2, -1,0,1 }; 37 const int wy[8] = { -1,-2,-1, 0,0, 1,2,1 }; 38 39 cv::Mat SX = cv::Mat::zeros( SrcImg.size(), CV_32F ); 40 cv::Mat SY = cv::Mat::zeros( SrcImg.size(), CV_32F ); 41 cv::Mat Mag = cv::Mat::zeros( SrcImg.size(), CV_32F ); 42 43 for( int cy=KernelR; cy+KernelR<SrcImg.rows; ++cy ) 44 { 45 float *pSY = SY.ptr<float>( cy ); 46 float *pSX = SX.ptr<float>( cy ); 47 float *pMag = Mag.ptr<float>( cy ); 48 for( int cx=KernelR; cx+KernelR<SrcImg.cols; ++cx ) 49 { 50 double Xdir=0, Ydir=0; 51 for( int i=0; i<8; ++i ) 52 { 53 double Ave = AreaAve( SrcImg, cx + dx[i]*S - S/2, cy + dy[i]*S - S/2, S ); 54 Xdir += Ave * wx[i]; 55 Ydir += Ave * wy[i]; 56 } 57 pSX[cx] = (float)(Xdir); 58 pSY[cx] = (float)(Ydir); 59 pMag[cx] = (float)sqrt( Xdir*Xdir + Ydir*Ydir ); 60 } 61 } 62 63 cv::imshow( "Src", SrcImg ); 64 Show32F( "SX", SX ); 65 Show32F( "SY", SY ); 66 Show32F( "Mag", Mag ); 67 cv::waitKey(); 68 return 0; 69}

投稿2021/02/04 01:47

編集2021/02/04 06:30
fana

総合スコア11985

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

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

yak_jun

2021/02/04 12:26

なるほど、結局疎であるという事は解像度低いのと変わらないと言うのはその通りと思っており、その頭で勝手にカーネルのストライドをカーネル幅と同一で考えていて、その頭で固定されておりました。 ストライドは1のままで動かせば線は太くぼやけた結果になりますが、こういう検知結果が得られるのは大きな前進となりそうです。(計算量の多さに関しては要検討ですが。。。) ありがとうございました。
guest

0

モルフォロジーは試したのですが、隙間が大きい箇所については膨張処理では補いきれませんでした。

kernelを大きくしてみるとか、iterationsを大きくしてみるとかのパラメータ調整で埋めてしまうこと自体は可能なはずです。

綺麗にエッジが取れるかはまた別の問題と言えるでしょう……。

投稿2021/02/03 19:05

hayataka2049

総合スコア30935

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

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

yak_jun

2021/02/04 01:10

回答ありがとうございます。 そうですね、埋めてしまう事自体は可能なのですが、どちらかと言うと隙間を埋めるというのは手段でしかなく、他のやり方も含めて色々と考えている状況であります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問