情報
https://www.pexels.com/photo/brown-wooden-flooring-hallway-176162/から使用された画像(「個人および商用利用は無料」)。
ソリューション TL;DR
ソーベル フィルターによるエッジ検出には、2 つの別個のフィルター操作が必要です。1 つのステップで行うことはできません。2 つの別々のステップの結果を組み合わせて、エッジ検出の最終結果を形成する必要があります。
(CV_32F)
情報:簡単にするためにフロート画像を使用しています。
コードでの解決策:
// Load example image
std::string path = "C:\\Temp\\SobelTest\\Lobby2\\";
std::string filename = "pexels-photo-176162 scaled down.jpeg";
std::string fqn = path + filename;
cv::Mat img = cv::imread(fqn, CV_LOAD_IMAGE_COLOR); // Value range: 0 - 255
// Convert to float and adapt value range (for simplicity)
img.convertTo(img, CV_32F, 1.f/255); // Value range: 0.0 - 1.0
// Build data for 3x3 vertical Sobel kernel
float sobelKernelHorizontalData[3][3] =
{
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}
};
// Calculate normalization divisor/factor
float sobelKernelNormalizationDivisor = 4.f;
float sobelKernelNormalizationFactor = 1.f / sobelKernelNormalizationDivisor;
// Generate cv::Mat for vertical filter kernel
cv::Mat sobelKernelHorizontal =
cv::Mat(3,3, CV_32F, sobelKernelHorizontalData); // Value range of filter result (if it is used for filtering): 0 - 4*255 or 0.0 - 4.0
// Apply filter kernel normalization
sobelKernelHorizontal *= sobelKernelNormalizationFactor; // Value range of filter result (if it is used for filtering): 0 - 255 or 0.0 - 1.0
// Generate cv::Mat for horizontal filter kernel
cv::Mat sobelKernelVertical;
cv::transpose(sobelKernelHorizontal, sobelKernelVertical);
// Apply two distinct Sobel filtering steps
cv::Mat imgFilterResultVertical;
cv::Mat imgFilterResultHorizontal;
cv::filter2D(img, imgFilterResultVertical, CV_32F, sobelKernelVertical);
cv::filter2D(img, imgFilterResultHorizontal, CV_32F, sobelKernelHorizontal);
// Build overall filter result by combining the previous results
cv::Mat imgFilterResultMagnitude;
cv::magnitude(imgFilterResultVertical, imgFilterResultHorizontal, imgFilterResultMagnitude);
// Write images to HDD. Important: convert back to uchar, otherwise we get black images
std::string filenameFilterResultVertical = path + "imgFilterResultVertical" + ".jpeg";
std::string filenameFilterResultHorizontal = path + "imgFilterResultHorizontal" + ".jpeg";
std::string filenameFilterResultMagnitude = path + "imgFilterResultMagnitude" + ".jpeg";
cv::Mat imgFilterResultVerticalUchar;
cv::Mat imgFilterResultHorizontalUchar;
cv::Mat imgFilterResultMagnitudeUchar;
imgFilterResultVertical.convertTo(imgFilterResultVerticalUchar, CV_8UC3, 255);
imgFilterResultHorizontal.convertTo(imgFilterResultHorizontalUchar, CV_8UC3, 255);
imgFilterResultMagnitude.convertTo(imgFilterResultMagnitudeUchar, CV_8UC3, 255);
cv::imwrite(filenameFilterResultVertical, imgFilterResultVerticalUchar);
cv::imwrite(filenameFilterResultHorizontal, imgFilterResultHorizontalUchar);
cv::imwrite(filenameFilterResultMagnitude, imgFilterResultMagnitudeUchar);
// Show images
cv::imshow("img", img);
cv::imshow("imgFilterResultVertical", imgFilterResultVertical);
cv::imshow("imgFilterResultHorizontal", imgFilterResultHorizontal);
cv::imshow("imgFilterResultMagnitude", imgFilterResultMagnitude);
cv::waitKey();
このコードは次と同等であることに注意してください。
cv::Sobel(img, imgFilterResultVertical, CV_32F, 1, 0, 3, sobelKernelNormalizationFactor);
cv::Sobel(img, imgFilterResultHorizontal, CV_32F, 0, 1, 3, sobelKernelNormalizationFactor);
cv::magnitude(imgFilterResultVertical, imgFilterResultHorizontal, imgFilterResultMagnitude);
結果画像
ソース画像、垂直フィルター結果、水平フィルター結果、複合フィルター結果 (大きさ)
OpenCV のデータ型と値の範囲に関する簡単な情報
- フロート画像 (画像タイプ
CV_32F
) の操作は、多くの場合非常に便利で、簡単な場合もあります。ただし、(uchar と比較して) 4 倍のデータが使用されるため、float 画像の処理も遅くなります。したがって、正確さと高いパフォーマンスが必要な場合は、uchar 画像のみを使用し、常に正しい除数 (パラメーター "alpha") を OpenCV 関数に渡す必要があります。ただし、これはエラーが発生しやすく、気付かないうちに値がオーバーフローする可能性があります。
- 8 ビット イメージ (uchar、CV_8UC) の値の範囲は 0 ~ 255 です。32 ビットの float イメージ (CV_32F) の値の範囲は 0.0 ~ 1.0 です (1.0 より大きい値は 1.0 と同じように表示されます)。オーバーフローが発生する可能性が低いため、32 ビット イメージを使用する方が簡単です (ただし、1.0 を超える値が発生するなど、不適切なスケーリングが発生する可能性があります)。
カーネル正規化除数の計算
カーネルの正規化除数は、次の式で計算できます。
f = max(abs(sumNegative), abs(sumPositive))
ここで、sumNegative はカーネル内の負の値の合計であり、sumPositive はカーネル内の正の値の合計です。
警告: これは と等しくありませんfloat normalizationDivisor = cv::sum(cv::abs(kernel))(0)
。このためのカスタム関数を作成する必要があります。
その他のヒント
- エッジ検出は解像度に依存するだけでなく、エッジの厚さに依存します。検出したいエッジがかなり厚い場合は、より大きなソーベル フィルター カーネル サイズを使用できます (大きなサイズのソーベル フィルター カーネルを参照してください。ただし、受け入れられている回答は使用しないでください。代わりに、(最も可能性が高い) 正しい回答である Adam Bowen の回答を使用してください)。 )。もちろん、画像を縮小して、デフォルトの 3x3 ソーベル フィルターを使用して太いエッジを検出することもできます。
- より大きなフィルター カーネルを使用すると、正規化の除数/係数が異なります。
- ソーベル フィルターは、近隣距離に関する大まかな概算にすぎません。Scharr フィルターは、「回転不変性を改善する」ため、Sobel フィルターよりも優れています [ http://johncostella.com/edgedetect/ ]
- 色付きの float 画像を保存するには、convertTo を使用してそれらを uchar に変換 (およびスケーリング) する必要があります。
カラー画像のエッジ検出
通常、カラー画像にエッジ検出フィルタを適用しても意味がありません。どのカラー チャネル (B、G、R) がエッジ検出にどの程度寄与するかを画像に表示し、この結果をカラー ピクセルに「エンコード」することは、非常に特殊で珍しい手順です。もちろん、あなたの目標が単に画像を「かっこよく」見せることである場合は、先に進んでください。この場合、ほとんどのルールはとにかく適用されません。
2018-04-24 更新
私が書いたことを繰り返し再考し、何年にもわたって画像フィルタリングに取り組んだ後、認めざるを得ません。カラー画像のエッジ検出が役立つ非常に有効で重要な理由があります。
簡単に言えば、グレーの画像では見えないエッジが画像にある場合、カラー画像でエッジ検出が必要になります。明らかに、これは (2 つの) 異なる色の領域の間のエッジに当てはまり、色はかなり区別できますが、グレー値は (ほぼ) 同じです。これは、人間として色で見ることに慣れているため、直感的ではない可能性があります。このようなユース ケースでアプリケーションを堅牢にしたい場合は、エッジ検出にグレー イメージではなくカラーを使用することをお勧めします。
カラー画像のフィルタリング手順により 3 チャネルのエッジ画像が生成されるため、結果を適切に単一の代表的なエッジ画像に変換する必要があります。
この変換ステップは、さまざまな方法で実行できます。 - 単純な平均化 - 画像の明るさを手動で計算するときに、B、G、および R チャネル (0.11、0.59、0.30) を重み付けするのと同じ方法で重み付けして計算します (これは、人間の知覚にすでに非常に近いエッジ画像になります) - それぞれの色の間で人間が知覚するコントラストを重み付けして計算します (これには LAB ベースのアプローチがあるかもしれません...) - 各ピクセルの最大値を使用します3チャンネルから - など
それは、正確に何を達成したいのか、これにどれだけの作業をしたいかによって異なります。一般に、平均化または RGB/BGR ベースの重み付けで十分です。