領域マスクで表される多角形の角を見つけます
-
19-09-2019 - |
質問
BW = poly2mask(x, y, m, n)
Vectors XおよびYで表されるROIポリゴンから、関心のあるバイナリ領域(ROI)マスク、BWを計算します。BWのサイズはm-by-nです。
poly2mask
ポリゴン(x、y)の内側にあるBWにピクセルを設定し、ポリゴンの外側にピクセルを0に設定します。
問題:このようなバイナリマスクが与えられたとすると、 BW
凸四角形の四隅を決定する最も効率的な方法は何でしょうか?
例えば。、
これまでの最良の解決策:使用 edge
境界線を見つけるには、ハフ変換を使用してエッジ イメージ内の 4 つのラインを見つけてから、それらの 4 つのラインの交点を見つけるか、エッジ イメージでコーナー検出器を使用します。複雑そうに見えますが、もっと簡単な解決策があるような気がしてなりません。
ところで、 convhull
常に 4 ポイントを返すとは限りません (おそらく誰かが提案できるでしょう) qhull
それを防ぐためのオプション):エッジに沿ったいくつかのポイントも返します。
編集:
アムロの答え とてもエレガントで効率的だと思われます。ただし、ピークは一意ではないため、実際の各コーナーには複数の「コーナー」が存在する可能性があります。に基づいてそれらをクラスタリングできます θ 実際のコーナーの周囲の「コーナー」を平均化しますが、主な問題は次の方法を使用することです。 order(1:10)
.
は 10
すべてのコーナーを説明するには十分ですか、それとも実際のコーナーの「コーナー」は除外されますか?
解決
これは何かに似ています @アンディ と提案した。ただし、接線の代わりに極座標の境界署名を使用しています。
まずエッジを抽出し、境界を取得して、それを署名に変換することに注意してください。最後に、重心から最も遠い境界上の点を見つけます。これらの点は、見つかったコーナーを構成します。(あるいは、コーナーのシグネチャ内のピークを検出することもできます)。
以下は完全な実装です。
I = imread('oxyjj.png');
if ndims(I)==3
I = rgb2gray(I);
end
subplot(221), imshow(I), title('org')
%%# Process Image
%# edge detection
BW = edge(I, 'sobel');
subplot(222), imshow(BW), title('edge')
%# dilation-erosion
se = strel('disk', 2);
BW = imdilate(BW,se);
BW = imerode(BW,se);
subplot(223), imshow(BW), title('dilation-erosion')
%# fill holes
BW = imfill(BW, 'holes');
subplot(224), imshow(BW), title('fill')
%# get boundary
B = bwboundaries(BW, 8, 'noholes');
B = B{1};
%%# boudary signature
%# convert boundary from cartesian to ploar coordinates
objB = bsxfun(@minus, B, mean(B));
[theta, rho] = cart2pol(objB(:,2), objB(:,1));
%# find corners
%#corners = find( diff(diff(rho)>0) < 0 ); %# find peaks
[~,order] = sort(rho, 'descend');
corners = order(1:10);
%# plot boundary signature + corners
figure, plot(theta, rho, '.'), hold on
plot(theta(corners), rho(corners), 'ro'), hold off
xlim([-pi pi]), title('Boundary Signature'), xlabel('\theta'), ylabel('\rho')
%# plot image + corners
figure, imshow(BW), hold on
plot(B(corners,2), B(corners,1), 's', 'MarkerSize',10, 'MarkerFaceColor','r')
hold off, title('Corners')
編集:Jacob のコメントに応えて、最初は 1/2 導関数を使用して署名内のピークを見つけようとしましたが、最終的には最も遠い N 点を取得したことを説明する必要があります。10 はその場限りの値であり、一般化するのは困難です (コーナーの数と同じ 4 を試してみましたが、すべてをカバーできませんでした)。それらをクラスタリングして重複を削除するというアイデアは検討する価値があると思います。
私が見る限り、最初のアプローチの問題は、次のことです。 rho
取らずに θ
考慮すると、異なる形状 (同じピークではない) が得られます。 スピード 境界をトレースする方法は異なり、曲率によって異なります。その方法がわかれば ノーマライズ その効果により、導関数を使用してより正確な結果を得ることができます。
他のヒント
持っている場合は、 画像処理ツールボックス, という機能があります。 cornermetric
これは、Harris コーナー検出器または Shi と Tomasi の最小固有値法を実装できます。この関数は、Image Processing Toolbox のバージョン 6.2 (MATLAB バージョン R2008b) 以降に存在します。
この機能を使用して、他の回答とは少し異なるアプローチを思いつきました。以下の解決策は、各「真の」コーナー点を中心とする円形領域は、実際にはエッジ上にある誤ったコーナー点を中心とする円形領域よりも少ない量で多角形と重なるという考えに基づいています。このソリューションは、同じコーナーで複数のポイントが検出される場合にも対応できます。
最初のステップはデータをロードすることです。
rawImage = imread('oxyjj.png');
rawImage = rgb2gray(rawImage(7:473, 9:688, :)); % Remove the gray border
subplot(2, 2, 1);
imshow(rawImage);
title('Raw image');
次に、次を使用してコーナー メトリックを計算します。 cornermetric
. 。元のポリゴンでコーナー メトリックをマスクしていることに注意してください。これにより、次のコーナー ポイントを探します。 内部 ポリゴン (すなわち、多角形の隅のピクセルを見つけようとしています)。 imregionalmax
次に、極大値を見つけるために使用されます。同じコーナー メトリックを持つ 1 ピクセルを超えるクラスターが存在する可能性があるため、最大値にノイズを追加して、各最大領域で 1 ピクセルのみが得られるように再計算します。次に、各最大領域は次を使用してラベル付けされます。 bwlabel
:
cornerImage = cornermetric(rawImage).*(rawImage > 0);
maxImage = imregionalmax(cornerImage);
noise = rand(nnz(maxImage), 1);
cornerImage(maxImage) = cornerImage(maxImage)+noise;
maxImage = imregionalmax(cornerImage);
labeledImage = bwlabel(maxImage);
次に、ラベル付けされた領域が拡張されます (次を使用します)。 imdilate
) 円盤状の構造要素 (を使用して作成) strel
):
diskSize = 5;
dilatedImage = imdilate(labeledImage, strel('disk', diskSize));
subplot(2, 2, 2);
imshow(dilatedImage);
title('Dilated corner points');
ラベル付きの角領域が拡張されたので、元の多角形と部分的に重なります。ポリゴンのエッジ上の領域は約 50% のオーバーラップを持ち、コーナー上の領域は約 25% のオーバーラップを持ちます。関数 regionprops
を使用して、ラベル付けされた各領域の重なり領域を見つけることができ、重なりの量が最も少ない 4 つの領域が真のコーナーと見なすことができます。
maskImage = dilatedImage.*(rawImage > 0); % Overlap with the polygon
stats = regionprops(maskImage, 'Area'); % Compute the areas
[sortedValues, index] = sort([stats.Area]); % Sort in ascending order
cornerLabels = index(1:4); % The 4 smallest region labels
maskImage = ismember(maskImage, cornerLabels); % Mask of the 4 smallest regions
subplot(2, 2, 3);
imshow(maskImage);
title('Regions of minimal overlap');
そして、次を使用してコーナーのピクセル座標を取得できるようになりました。 find
そして ismember
:
[r, c] = find(ismember(labeledImage, cornerLabels));
subplot(2, 2, 4);
imshow(rawImage);
hold on;
plot(c, r, 'r+', 'MarkerSize', 16, 'LineWidth', 2);
title('Corner points');
そして、これはダイヤモンド型の領域を使用したテストです。
私は、境界を使用してこの問題を解決することを好みます。境界を使用すると、問題が 2D 問題から 1D 問題に削減されるからです。
使用 bwtraceboundary()
画像処理ツールキットから境界上の点のリストを抽出します。次に、境界を一連の接線ベクトルに変換します (これを行う方法はいくつかありますが、1 つの方法は、i
から境界に沿った 3 番目の点 i+delta
点目。) ベクトルのリストを取得したら、隣接するベクトルの内積を求めます。点積が最も小さい 4 つの点がコーナーです。
アルゴリズムを任意の数の頂点を持つポリゴンで動作させたい場合は、単純に、ドット積の中央値より一定の標準偏差の数だけ下にあるドット積を検索します。
を使用することにしました ハリスコーナー検出器 (ここにあります より正式な説明) 角を取得します。これは次のように実装できます。
%% Constants
Window = 3;
Sigma = 2;
K = 0.05;
nCorners = 4;
%% Derivative masks
dx = [-1 0 1; -1 0 1; -1 0 1];
dy = dx'; %SO code color fix '
%% Find the image gradient
% Mask is the binary image of the quadrilateral
Ix = conv2(double(Mask),dx,'same');
Iy = conv2(double(Mask),dy,'same');
%% Use a gaussian windowing function and compute the rest
Gaussian = fspecial('gaussian',Window,Sigma);
Ix2 = conv2(Ix.^2, Gaussian, 'same');
Iy2 = conv2(Iy.^2, Gaussian, 'same');
Ixy = conv2(Ix.*Iy, Gaussian, 'same');
%% Find the corners
CornerStrength = (Ix2.*Iy2 - Ixy.^2) - K*(Ix2 + Iy2).^2;
[val ind] = sort(CornerStrength(:),'descend');
[Ci Cj] = ind2sub(size(CornerStrength),ind(1:nCorners));
%% Display
imshow(Mask,[]);
hold on;
plot(Cj,Ci,'r*');
ここでは、強度の変化を滑らかにするガウス ウィンドウ関数のおかげで、複数のコーナーの問題が発生します。以下は、コーナーの拡大版です。 hot
カラーマップ。
Ruby を使用した例を次に示します。 スズメバチ目. 。基本的に、プログラムは量子化されたソーベル勾配方向のヒストグラムを作成して、支配的な方向を見つけます。4 つの主要な方向が見つかった場合は、線が当てはめられ、隣接する線間の交点が投影された長方形の角であると見なされます。
#!/usr/bin/env ruby
require 'hornetseye'
include Hornetseye
Q = 36
img = MultiArray.load_ubyte 'http://imgur.com/oxyjj.png'
dx, dy = 8, 6
box = [ dx ... 688, dy ... 473 ]
crop = img[ *box ]
crop.show
s0, s1 = crop.sobel( 0 ), crop.sobel( 1 )
mag = Math.sqrt s0 ** 2 + s1 ** 2
mag.normalise.show
arg = Math.atan2 s1, s0
msk = mag >= 500
arg_q = ( ( arg.mask( msk ) / Math::PI + 1 ) * Q / 2 ).to_int % Q
hist = arg_q.hist_weighted Q, mag.mask( msk )
segments = ( hist >= hist.max / 4 ).components
lines = arg_q.map segments
lines.unmask( msk ).normalise.show
if segments.max == 4
pos = MultiArray.scomplex *crop.shape
pos.real = MultiArray.int( *crop.shape ).indgen! % crop.shape[0]
pos.imag = MultiArray.int( *crop.shape ).indgen! / crop.shape[0]
weights = lines.hist( 5 ).major 1.0
centre = lines.hist_weighted( 5, pos.mask( msk ) ) / weights
vector = pos.mask( msk ) - lines.map( centre )
orientation = lines.hist_weighted( 5, vector ** 2 ) ** 0.5
corner = Sequence[ *( 0 ... 4 ).collect do |i|
i1, i2 = i + 1, ( i + 1 ) % 4 + 1
l1, a1, l2, a2 = centre[i1], orientation[i1], centre[i2], orientation[i2]
( l1 * a1.conj * a2 - l2 * a1 * a2.conj -
l1.conj * a1 * a2 + l2.conj * a1 * a2 ) /
( a1.conj * a2 - a1 * a2.conj )
end ]
result = MultiArray.ubytergb( *img.shape ).fill! 128
result[ *box ] = crop
corner.to_a.each do |c|
result[ c.real.to_i + dx - 1 .. c.real.to_i + dx + 1,
c.imag.to_i + dy - 1 .. c.imag.to_i + dy + 1 ] = RGB 255, 0, 0
end
result.show
end