画像内のシマウマのようなパターンの検出(写真からの構造化された光の縞の中心線の検出)


12

私は、被写体に対してフリンジを投影し、写真を撮るプロジェクトに取り組んでいます。タスクは、フリンジの中心線を見つけることです。フリンジの中心線は、フリンジ平面と被写体表面の間の交差の3D曲線を数学的に表します。

写真はPNG(RGB)であり、以前の試みでは、グレースケールと差分しきい値を使用して、白黒の「ゼブラのような」写真を取得し、そこから各フリンジの各ピクセル列の中間点を簡単に見つけました。問題は、しきい値処理と離散ピクセル列の平均高さの取得により、精度の低下と量子化が発生することです。これはまったく望ましくありません。

私の印象では、画像を見ると、いくつかの統計的掃引法によって、しきい値なしの画像(RGBまたはグレースケール)から直接検出された場合、中心線はより連続的(より多くのポイント)およびより滑らか(量子化されない)になる可能性があります(いくつかのフラッディング/反復畳み込み、何でも)。

以下は実際のサンプル画像です。

ここに画像の説明を入力してください

どんな提案でも大歓迎です!


それは非常に興味深いです。しかし、ちなみに、私はカラーストライプを使用して3Dオブジェクトを検出する研究を行っています。カラーストライプを使用しているため、プロジェクターから各ストライプの対応を簡単に見つけることができます。そのため、三角法を使用して3D情報を計算できます。色が同じ場合、どのように対応を見つけますか?あなたのプロジェクトも3D再構成についてですか?

@johnyoung:回答としてコメントを追加しないでください。コメントする前に評判が必要だと思いますが、現在の行動を控えてください。自分の(関連する)質問をするか、他の人の質問に答えて担当者を増やしてください。
ピーターK。

答えを出す代わりにもう1つの質問で申し訳ありませんが、位相シフト法では投影画像の各ピクセルで位相を計算しますが、ここでフリンジの中心線を見つける必要がある理由は、私の質問はばかげているかもしれませんが、私はしませんいいえ、正確な理由を教えてください。Uは答えを与えた後、私の質問を削除することができます

これらは異なる方法です。一連の白いストライプ(それぞれが3D空間で「平面」を形成する)を投影することにより、一連の幾何学的平面をモデリングしています。したがって、プレーンには厚みがないため、フリンジの中心線を見つける必要があります。確かに位相シフト解析を実行できましたが、1つの問題があります:私の投影はバイナリ(交互に黒と白のストライプ)であり、強度は正弦波的に変化しないため、位相シフトを実行できません(現在、する必要はありません) )。
heltonbiker 14年

回答:


13

次の手順をお勧めします。

  1. 前景を背景から分離するためのしきい値を見つけます。
  2. バイナリイメージの各ブロブ(1つのゼブラストライプ)について、それぞれについてxy方向の重み付き中心(ピクセル強度による)を見つけます。
  3. yノイズを除去するために、おそらく値を滑らかにします。
  4. (x,y)ある種の曲線をあてはめて、ポイントを接続します。この記事はあなたを助けるかもしれません。私の意見では、より高レベルの多項式を適合させることもできますが、それはさらに悪いことです。

以下は、ステップ1、2、および4を示すMatlabコードです。自動しきい値選択はスキップしました。代わりに、マニュアルを選択しましたth=40

これらは、列ごとの加重平均を見つけることによって見つけられる曲線です: ここに画像の説明を入力してください

これらは、多項式を近似した後の曲線です。 ここに画像の説明を入力してください

コードは次のとおりです。

function Zebra()
    im = imread('http://i.stack.imgur.com/m0sy7.png');
    im = uint8(mean(im,3));

    th = 40;
    imBinary = im>th;
    imBinary = imclose(imBinary,strel('disk',2));
    % figure;imshow(imBinary);
    labels = logical(imBinary);
    props =regionprops(labels,im,'Image','Area','BoundingBox');

    figure(1);imshow(im .* uint8(imBinary));
    figure(2);imshow(im .* uint8(imBinary));

    for i=1:numel(props)
        %Ignore small ones
        if props(i).Area < 10
            continue
        end
        %Find weighted centroids
        boundingBox = props(i).BoundingBox;
        ul = boundingBox(1:2)+0.5;
        wh = boundingBox(3:4);
        clipped = im( ul(2): (ul(2)+wh(2)-1), ul(1): (ul(1)+wh(1)-1) );
        imClip = double(props(i).Image) .* double(clipped);
        rows = transpose( 1:size(imClip,1) );
        %Weighted calculation
        weightedRows  = sum(bsxfun(@times, imClip, rows),1) ./ sum(imClip,1);
        %Calculate x,y
        x = ( 1:numel(weightedRows) ) + ul(1) - 1;
        y = ( weightedRows ) + ul(2) - 1;
        figure(1);
        hold on;plot(x,y,'b','LineWidth',2);
        try %#ok<TRYNC>
            figure(2);
            [xo,yo] = FitCurveByPolynom(x,y);
            hold on;plot(xo,yo,'g','LineWidth',2);
        end
        linkaxes( cell2mat(get(get(0,'Children'),'Children')) )
    end        
end

function [xo,yo] = FitCurveByPolynom(x,y)
   p = polyfit(x,y,15); 
   yo = polyval(p,x);
   xo = x;
end

これは非常に興味深いことがわかりました。私はPythonを使用していますが、とにかくこのすべての理論的根拠を研究する必要があります。独立したコメントとして、私は古典的な画像処理(uint8配列などの量子化された画像コンテナーで直接)を実行する傾向はありませんが、代わりに操作を適用する前にすべてを浮動配列としてメモリにロードします。また、画像の下半分の結果にも驚いています。青い線が予想されるフリンジの正中線に沿って走っていません...(?)。おかげさまで、結果が出たらすぐにフィードバックをお届けします!
heltonbiker

@heltonbiker、更新された回答を確認してください。あなたは浮動小数点について正しいです、私はに変換したときにそれを使用しましたdouble。下半分での結果については、私がチェックする必要があり、それはソフトウェアのバグかもしれない
アンドレイRubshtein

1
@heltonbiker、できました。それは確かに1ベースのインデックス付けに関連するバグでした。
アンドレイRubshtein

素晴らしい!本当に素晴らしい。この手法を使用すると、私の目的では、スムージングは​​必要ないだけでなく、有害にもなります。ご関心をお寄せいただきありがとうございます!
heltonbiker

3

RGBイメージは使用しません。カラー画像は通常、カメラセンサーに「バイエルフィルター」を配置することで作成されます。これにより、通常、達成できる解像度が低下します。

グレースケール画像を使用する場合、説明した手順(「ゼブラ」画像の二値化、正中線の検出)が良いスタートだと思います。最後のステップとして、私は

  • あなたが見つけた正中線の各ポイントを取る
  • 「ゼブラ」ラインの上下のピクセルのグレー値を取得します
  • 最小平均二乗を使用してこれらのグレー値に放物線を当てはめる
  • この放物線の頂点は、正中線位置の改善された推定値です

いい考えだ。各ピクセル列のピーク値に沿ってある種の放物線またはスプラインを使用する予定ですが、ピクセル列を調べるか、代わりに行に沿ったピクセル「領域」を調べるべきかどうか疑問に思っています。より多くの答え。どうもありがとう!
heltonbiker

@heltonbiker-簡単なテストとして、緑のチャンネルのみを使用します。通常、カラーセンサーには2倍の緑のピクセルがあり、赤と青よりも補間が少ない
Martin Beckett

@MartinBeckett関心をお寄せいただきありがとうございます。各チャネルを既に分析しました。実際、緑色のチャネルは、たとえば赤色のチャネルよりもはるかに解決されているようです。ただし、各チャネルの垂直断面の強度値をプロットすると、「ストライプパターン」はチャネル間でそれほど変化しないようであり、現在、グレースケールへの変換時にそれらを等しく混合しています。それでも、チャンネル間の最良の線形結合を調べて、最高のコントラストの結果を得るか、または既にグレースケールの画像を取得する予定です。再度、感謝します!
heltonbiker

3

ここでは、質問を「パス最適化問題」としてモデル化することによる、問題の代替ソリューションを示します。単純な二値化と曲線化のソリューションよりも複雑ですが、実際にはより堅牢です。

非常に高いレベルから、この画像をグラフとして考える必要があります。

  1. 各画像ピクセルは、このグラフ上のノードです

  2. 各ノードは近隣ノードと呼ばれる他のノードに接続され、この接続定義は多くの場合、このグラフのトポロジと呼ばれます。

  3. 各ノードには重み(機能、コスト、エネルギー、または任意の名前)があり、このノードが探している最適な中心線にある可能性を反映しています。

この尤度をモデル化できる限り、「フリンジの中心線」を見つける問題は、グラフ上のローカル最適パス見つける問題になります。これは、動的プログラミング、たとえばビタビアルゴリズムによって効果的に解決できます。

このアプローチを採用する長所は次のとおりです。

  1. すべての結果は連続します(1つの中心線を分割する可能性のあるしきい値方法とは異なります)

  2. そのようなグラフを作成するための多くの自由、さまざまな機能、およびグラフトポロジを選択できます。

  3. パス最適化の意味で結果は最適です

  4. ノイズがすべてのピクセルに均等に分配されている限り、それらの最適なパスは安定したままであるため、ソリューションはノイズに対してより堅牢になります。

上記のアイデアの簡単なデモを次に示します。事前の知識を使用して、可能な開始ノードと終了ノードを指定しないため、可能なすべての開始ノードを単純にデコードします。 デコードされたビタビパス

ファジーエンドについては、考えられるすべての終了ノードの最適なパスを探しているという事実が原因です。その結果、暗い領域にある一部のノードでは、強調表示されたパスは依然としてそのローカル最適パスです。

ファジーパスの場合は、見つかった後にスムージングするか、生の強度の代わりにスムージングされた機能を使用できます。

開始ノードと終了ノードを変更することにより、部分的なパスを復元することができます。

これらの望ましくないローカル最適パスを除去することは難しくありません。ビタビ復号後のすべてのパスの可能性があるため、さまざまな事前知識を使用できます(たとえば、同じソースを共有する場合に必要な最適なパスは1つだけであることがわかります)。

詳細については、論文を参照してください。

 Wu, Y.; Zha, S.; Cao, H.; Liu, D., & Natarajan, P.  (2014, February). A Markov Chain Line Segmentation Method for Text Recognition. In IS&T/SPIE 26th Annual Symposium on Electronic Imaging (DRR), pp. 90210C-90210C.

上記のグラフを作成するために使用するPythonコードの短い部分を次に示します。


import cv2
import numpy as np
from matplotlib import pyplot
# define your image path
image_path = ;
# read in an image
img = cv2.imread( image_path, 0 );
rgb = cv2.imread( image_path, -1 );

# some feature to reflect how likely a node is in an optimal path
img = cv2.equalizeHist( img ); # equalization
img = img - img.mean(); # substract DC
img_pmax = img.max(); # get brightest intensity
img_nmin = img.min(); # get darkest intensity
# express our preknowledge
img[ img > 0 ] *= +1.0  / img_pmax; 
img[ img = 1 :
    prev_idx = vt_path[ -1 ].astype('int');
    vt_path.append( path_buffer[ prev_idx, time ] );
    time -= 1;
vt_path.reverse();    
vt_path = np.asarray( vt_path ).T;

# plot found optimal paths for every 7 of them
pyplot.imshow( rgb, 'jet' ),
for row in range( 0, h, 7 ) :
    pyplot.hold(True), pyplot.plot( vt_path[row,:], c=np.random.rand(3,1), lw = 2 );
pyplot.xlim( ( 0, w ) );
pyplot.ylim( ( h, 0 ) );

これは非常に興味深いアプローチです。「グラフ」のトピックは、最近まで(この同じプロジェクトで)グラフを使用して別の問題を解決することしかできなかったときまで、私にはあいまいだったことを告白します。「わかった」後、これらの最短パスアルゴリズムがどれほど強力であるかを認識しました。あなたのアイデアは非常に興味深いものであり、必要性/機会があればこのアイデアを再実装することは不可能ではありません。どうもありがとうございました。
heltonbiker 14

現在の結果については、私の経験から、グラフを作成する前に、まずガウスフィルターやメディアンフィルターで画像を平滑化することをお勧めします。これにより、より滑らかな(より正確な)ラインが得られます。また、可能なトリックの1つは、2つ以上のピクセル(指定された制限、たとえば8または10ピクセルまで)を「直接ジャンプ」できるように近傍を拡張することです。もちろん、適切なコスト関数を選択する必要がありますが、調整は簡単だと思います。
heltonbiker 14

そうそう。手元にあるものを選んだだけで、他のトポロジーとエネルギー関数を間違いなく使用できます。実際、このフレームワークもトレーニング可能です。特に、生の強度から開始し、最適なパスをデコードし、信頼性の高い最適なノードのみを選択します。この方法で「ラベル付きデータ」を取得します。自動的にラベル付けされたデータのこの小さな部分で、多くの種類の有用なことを学ぶことができます。
落とし穴14

3

他のアプローチとは少し違うので、答えを投稿すべきだと思った。Matlabでこれを試しました。

  • すべてのチャンネルを合計して画像を作成し、すべてのチャンネルに均等に重み付けします
  • この画像に対して形態学的なクロージングとガウスフィルタリングを実行する
  • 結果の画像の各列について、極大値を見つけて画像を構築します
  • この画像の連結成分を見つける

私がここで見る欠点の1つは、ストライプの向きによってはこのアプローチがうまく機能しないことです。その場合、向きを修正してこの手順を適用する必要があります。

Matlabコードは次のとおりです。

im = imread('m0sy7.png');
imsum = sum(im, 3); % sum all channels
h = fspecial('gaussian', 3);
im2 = imclose(imsum, ones(3)); % close
im2 = imfilter(im2, h); % smooth
% for each column, find regional max
mx = zeros(size(im2));
for c = 1:size(im2, 2)
    mx(:, c) = imregionalmax(im2(:, c));
end
% find connected components
ccomp = bwlabel(mx);

たとえば、画像の中央の列を取得する場合、そのプロファイルは次のようになります(青はプロファイルです。緑は局所的最大値です)。 中間プロファイルと極大

そして、すべての列の極大値を含む画像は次のようになります。 ここに画像の説明を入力してください

接続されたコンポーネントは次のとおりです(一部のストライプは壊れていますが、それらのほとんどは連続した領域を取得します)。

ここに画像の説明を入力してください


これは実際に現在行っていることですが、唯一の違いは各ピクセル列の局所的最大値を見つける方法です:放物線補間を使用して、最大値とその上下のピクセルを通過する放物線の正確な頂点を見つけます。これにより、結果のsを「ピクセル間」ピクセルにすることができ、線の微妙な滑らかさをよりよく表します。ご回答有難うございます!
heltonbiker 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.