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

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

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

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

OpenCV

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

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

1519閲覧

2値画像の形状を強調する方法

fana

総合スコア11654

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

OpenCV

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

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

1クリップ

投稿2019/08/05 02:29

編集2019/08/06 02:34

目的

2値画像の形状を強調したいです.

入力2値画像(図1左上)を適当なサイズのガウシアンフィルタでぼかして(図1右上)から
(白と黒の中間値を閾値として)2値化すれば「形状が丸く」なります(図1右下).
とがっていた部分は丸く,輪郭部における白と黒のせめぎ合い具合が弱くなります.

やりたいことはこれとは逆方向の処理です.
つまり,白と黒が互いにより浸食し合うような形状を生成する方法を得たいです.
結果形状のイメージを図2に赤線で示します(これはイメージを手で描いたものです).

処理方法をご教示願いたく,宜しくお願い申し上げます.

図1

左上:入力,右上:ガウシアンフィルタでぼかしたもの
左下:後述のやってみた方法の結果,右下:形状が丸くなる処理の結果
Fig1

図2:欲しい処理結果のイメージ

Fig2

自身でやったこと

輪郭の座標値をオフセットする方法を思いつき,試したのですが,処理結果(図1左下)は思わしくありません.

  • 輪郭形状が変.
    全体的に細かくギザギザしているし,先端部はふにゃふにゃな形になっている.
  • 図内左上の円形が大きくなってしまうのが所望の結果と異なる.

また,できればこのような方法ではなくて,
「丸く」する側の処理のような,輪郭座標値を陽に扱わない方法が希望です.
(ぼかした結果に対して画素毎に異なる閾値を与えて2値化すれば所望の形状が得られるのではないか?と考えているのですが,
肝心の妥当な閾値を決める処理をどうすればよいのか?というところで行き詰っております.)

C++

1//思いつきでやってみた形状強調処理(結果は思わしくない) 2// 3//処理概要: 4// 入力Contour[k]に関する,結果Result[k]は, 5// 近隣(Contour[k-Wnd]~Contour[k+Wnd])の座標平均GとContour[k]との差deltaを用いて, 6// Result[k] = Contour[k] + Rate*delta 7// where delta = Contour[k] - G 8// ただし,deltaが閾値以下の場合は 9// Result[k] = Contour[k] 10// とする. 11//[Args] 12// Contour : 入力形状(外周画素群の座標) 13// WndR : 参照する近隣範囲 14// Rate : 強調率 15// IgnoreDeltaThresh : ごく小さい変化を強調しない用の閾値 16//[Ret] 17// 結果形状(外周画素群の座標) 18std::vector<cv::Point> EnhanceShape( const std::vector< cv::Point > &Contour, int WndR, float Rate, float IgnoreDeltaThresh=1.0f ) 19{ 20 WndR = std::max( 1, WndR ); 21 const int WndSize = 2*WndR + 1; 22 const float SqThresh = IgnoreDeltaThresh * IgnoreDeltaThresh; 23 24 const int N = (int)Contour.size(); 25 std::vector< cv::Point > Result = Contour; 26 27 for( int i=0; i<N; ++i ) 28 { 29 //近隣重心G 30 int SumX=0; 31 int SumY=0; 32 for( int k=-WndR; k<=WndR; k++ ) 33 { 34 int kk = i+k; 35 if( kk>=N )kk-=N; 36 if( kk<0 )kk+=N; 37 38 SumX += Contour[kk].x; 39 SumY += Contour[kk].y; 40 } 41 float GX = (float)SumX / WndSize; 42 float GY = (float)SumY / WndSize; 43 //delta 44 float dx = Contour[i].x - GX; 45 float dy = Contour[i].y - GY; 46 //Result 47 if( dx*dx + dy*dy > SqThresh ) 48 { 49 Result[i].x += cvRound( Rate*dx ); 50 Result[i].y += cvRound( Rate*dy ); 51 } 52 } 53 return Result; 54} 55 56int main() 57{ 58 //原画(2値画像とする) 59 cv::Mat Src = cv::imread( "Blobs.png", cv::IMREAD_GRAYSCALE ); 60 cv::imshow( "Src", Src ); 61 62 {//形状が丸くなる処理 63 cv::Mat Blurred; 64 const int KernelSize = 41; 65 cv::GaussianBlur( Src, Blurred, cv::Size(KernelSize,KernelSize), 0 ); 66 cv::imshow( "Blurred", Blurred ); 67 68 cv::Mat Rounded; 69 cv::threshold( Blurred, Rounded, 128, 255, cv::THRESH_BINARY ); 70 cv::imshow( "Rounded(Thresh128)", Rounded ); 71 } 72 73 {//形状を強調したい 74 //外周画素座標をいじくってみるテスト 75 std::vector< std::vector< cv::Point > > Contours; 76 cv::findContours( Src, Contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE ); 77 for( auto &Cont : Contours ) 78 { Cont = EnhanceShape( Cont, 15, 4 ); } 79 80 {//結果表示 81 cv::Mat Show( Src.rows, Src.cols, CV_8UC3 ); 82 Show = cv::Scalar(255,0,0); 83 Show.setTo( cv::Scalar(0,96,0), Src ); 84 for( int i=0; i<(int)Contours.size(); ++i ) 85 { cv::drawContours( Show, Contours, i, cv::Scalar(0,0,255) ); } 86 cv::imshow( "TestResult", Show ); 87 } 88 } 89 90 cv::waitKey(); 91 return 0; 92}

やったことの追記

ぼかした結果に対して画素毎に異なる閾値を与えて2値化

に関して,質問後に試行した内容を追記しておきます.(もちろん,解決していません.)

各位置の閾値を高くすべきか低くすべきか?のヒントは,元画像と形状を丸めた画像との差分から得られるであろう,と考えました.
(元画像で白だった部分が丸めた結果黒になったならば,その逆処理を行うにはその付近の閾値を小さく与えれば良く,元画像で黒だった部分が白に変わった箇所の付近では閾値を大きく与えればよい)
問題は,「付近」にその情報をどのようにして伝達すればよいか?という部分で,ここがまだ未解決です.

図3は,とりあえず簡単に試せる伝達手段として,モルフォロジとガウシアンフィルタを用いてみた結果です.
図3左が閾値マップで,図3右がそれを用いて図1右上のぼかし画像を2値化した結果です.
大まかな形はなんとなく図2の方向に向かっているようにも見えますが,
元の形状でとがっていた部分が残念な形になっています→これは閾値マップ生成時の「付近への伝達」手段に点広がり的な方法を用いたためであると思います.
何か指向性のある(?)伝達手段を用意できれば,改善するのかもしれません.

図3:変動閾値試行結果

イメージ説明

退会済みユーザー👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

次の2通りの方法があると思います。

  1. 輪郭抽出後、輪郭に対して処理を行い理想の形にする。
  2. 2値画像を理想の形にしてから輪郭抽出する。

2の方法で少し考えてみました。
図2として記載されている形には現状できなかったのですが、distanceTransform() で距離画像を作成した後、うまく処理をして抽出したい輪郭の形の2値画像を作成できないかと考えました。

結局、いいアイデアが思いつかずうまくいっていないのですが、現状の結果を載せておきます。
なにか考えが浮かんだら追記します。

OpenCV - distanceTransform() で距離変換を行う

python

1import cv2 2import numpy as np 3import matplotlib.pyplot as plt 4 5binary = cv2.imread("sample.png", cv2.IMREAD_GRAYSCALE) 6 7# NOT 演算を適用する。 8binary = cv2.bitwise_not(binary) 9 10# 輪郭抽出する。 11contours, hierarchy = cv2.findContours( 12 binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE 13) 14 15dst = np.zeros_like(binary) # 結果の2値画像 16 17# 輪郭ごとに処理する。 18for cnt in contours: 19 # マスク画像を作成する。 20 mask = np.zeros_like(binary) 21 cv2.drawContours(mask, [cnt], -1, color=255, thickness=-1) 22 23 # 距離画像を作成する。 24 dist = cv2.distanceTransform(mask, cv2.DIST_L2, 3) 25 26 # 距離画像の値が一定以上かどうかで2値化する。 27 ret, thresh = cv2.threshold(dist, dist.max() * 0.3, 255, cv2.THRESH_BINARY) 28 29 # 結果の2値画像に追加する。 30 dst = cv2.bitwise_or(dst, thresh.astype(np.uint8)) 31 32 # 可視化する。 33 fig = plt.figure(figsize=(10, 5)) 34 35 ax1 = fig.add_subplot(1, 2, 1) 36 ax1.set_title("mask") 37 ax1.imshow(mask, cmap="gray") 38 39 ax2 = fig.add_subplot(1, 2, 2) 40 ax2.set_title("distance image") 41 ax2.imshow(dist) 42 43# 輪郭抽出する。 44contours, hierarchy = cv2.findContours(dst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 45 46# 輪郭を描画する。 47copy = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR) 48cv2.drawContours(copy, contours, -1, color=(0, 255, 0), thickness=2) 49 50# 可視化する。 51fig, ax = plt.subplots() 52ax.imshow(copy) 53plt.show()

イメージ説明

3つのオブジェクトのうちの1つの距離画像

イメージ説明

結果

追記

他に試したこと

輪郭抽出したあと、重心を求めて、凹んでる部分は重心側に、凸の部分は重心と反対側に引っ張ってみましたが、あまりうまくいきませんでした。

イメージ説明

投稿2019/08/05 07:47

編集2019/08/08 15:40
tiitoi

総合スコア21956

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

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

fana

2019/08/06 01:03

検討いただき感謝です. 目的の処理を「丸くする」処理の逆変換だと考えれば,閾値処理とガウシアンの畳み込みの逆処理を書ければ何かできそうかな?とか思っていたのですが, >距離画像 を使えば閾値処理の逆の処理を疑似的に書けそうですね.輪郭からの距離に応じたガウス関数値を画素値とする絵を作る感じで. (ガウシアン畳み込みの逆は…ウィナーフィルタ?とか何とかで周波数の世界で戦う話になりそうですが,無学ゆえに難易度高い…)
tiitoi

2019/08/08 15:41 編集

力になれず、すみません。自分も信号解析等は詳しくないのです。 今回の課題は図1左上 → 図2ですが、その逆の図2 → 図1左上もどういう処理をかけたらそのような形になるのかというのが見えないです。 (図2をガウシアンフィルタ等でぼかしても図1左上の形にはならなそう) 他のアイデアとして、もし図2 → 図1左上の処理がわかれば、変換前と変換後のペアの画像は作れるので、CNN 使えばその逆の変換も学習できるだろうと思いました。 (できれば画像処理で方法を見つけたいですが。。。) また追記のように輪郭の位置情報を使ったやり方なども試してみましたが、うまくいきませんでした。
fana

2019/08/09 01:22

お力添えいただき感謝しております. 輪郭形状をオフセットする系の方法を考える際,その支点的なもの(?)として図形の重心を用いるのだと,適用できる図形形状に大きな制約が生じてしまうのが悩みどころですよね. (着目輪郭点の周辺だけを使ったローカルな重心を使えばそのあたりの制約が緩和できそうですが,そうすると私が最初にやったことと極めて近くなるので悩ましいという…) ひょっとしたら,出力輪郭形状というのは,入力図形の輪郭形状を生成するためのベジェの制御点群みたいなのが近いのかも?とか思ったりもしましたが…それも自力で試行できず…^^;
fana

2019/08/09 01:42

このサイトで言うところの「解決」はしていない状態ではありますが(そもそも解があるかどうかもわからない話ですし),このまま放置しとくのもいかんと思いますし,他の方から回答いただけるような気配も感じませんので(母集団のサイズが1な中での「ベストなアンサー」という意味で)ベストアンサーとさせて頂きます. また,distanceTransformを用いる方法の可能性を示して頂いたことに関して,今後の検討に大いに参考になりそうですので高評価とさせて頂きます.感謝.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問