我尝试使用OpenCV重建之前在Gimp中完成的一些预处理。第一阶段是用于边缘检测的Sobel滤波器。它在Gimp中非常有效:
现在我的尝试是OpenCV:
opencv_imgproc.Sobel(/* src = */ scaled, /* dst = */ sobel,
/* ddepth = */ opencv_core.CV_32F,
/* dx = */ 1, /* dy = */ 1, /* ksize = */ 5, /* scale = */ 0.25,
/* delta = */ 0.0, /* borderType = */ opencv_core.BORDER_REPLICATE)
看起来非常糟糕,主要是突出点而不是轮廓:
那么我做错了什么,或者Gimp如何实现如此好的结果?如何在OpenCV中复制它?
答案 0 :(得分:11)
图片使用https://www.pexels.com/photo/brown-wooden-flooring-hallway-176162/(“免费用于个人和商业用途”)。
通过Sobel滤波器进行边缘检测需要两个单独的滤波器操作。 无法一步完成。 两个独立步骤的结果必须结合起来形成边缘检测的最终结果。
信息:为简单起见,我使用浮动图像(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);
源图像,垂直滤波器结果,水平滤波器结果,组合滤波器结果(幅度)
CV_32F
)通常非常有用,有时更简单。但是,使用浮动图像
由于使用了4倍的数据(与uchar相比),因此速度也较慢。所以,如果你想要正确性和高性能,
您将不得不仅使用uchar图像并始终将正确的除数(参数“alpha”)传递给OpenCV函数。
但是,这更容易出错,并且可能会发生在您没有意识到的情况下您的值会溢出的情况。内核的归一化除数可以通过以下公式计算:
f = max(abs(sumNegative), abs(sumPositive))
其中sumNegative是内核中负值的总和,sumPositive是内核中正值的总和。
警告:这不等于float normalizationDivisor = cv::sum(cv::abs(kernel))(0)
,您必须为此编写自定义函数。
<击> 在彩色图像上应用边缘检测滤波器通常没有意义。 使图像显示哪个颜色通道(B,G,R)对边缘检测有多大贡献并将该结果“编码”成彩色像素是非常特定且不常见的过程。 当然,如果您的目标只是让图像看起来“酷”,那就继续吧。在这种情况下,大多数规则无论如何都不适用。 击>
更新2018-04-24
多次重复思考我所写的内容并使用图像过滤多年来我不得不承认:有很多有效且重要的原因,彩色图像上的边缘检测很有用。
简单地说:如果图像中有灰色图像中不可见的边缘,则需要对彩色图像进行边缘检测。 显然,情况是(两个)不同颜色区域之间的边缘,其中颜色是相当可区分的,而它们的灰色值将(大致)相同。 这可能不直观地发生,因为我们习惯于以人的眼光看待颜色。 如果您的应用程序想要在这种用例中保持稳健,您应该更喜欢使用颜色而不是灰色图像进行边缘检测。
由于彩色图像上的滤波步骤产生3通道边缘图像,因此必须将结果合理地转换为单个代表性边缘图像。
此转换步骤可以通过多种方式完成: - 简单平均 - 当手动计算图像的亮度时(这将导致边缘图像已经非常接近人类感知),通过加权以与加权B,G和R通道(0.11,0.59,0.30)相同的方式进行计算 - 通过加权计算各个颜色之间人为感知的对比度(可能有一些基于LAB的方法到那里......) - 使用3个通道中每个像素的最大值 - 等等。
这取决于您想要达到的目标以及您想要投入多少工作。 通常,平均或基于RGB / BGR的加权就足够了。
答案 1 :(得分:1)
Sobel通常用于X和Y方向,然后组合以产生每像素的2D矢量。也就是说,它给出了2D中每个像素的渐变(如果你已经得到了这个,那就道歉了,但它使得我要说的更清楚)。
在单个像素中如何精确地表示2D矢量是可以解释的。从这些图像来看,OpenCV看起来比Gimp更突出水平线,而Gimp比OpenCV更突出显示垂直线。
鉴于您的图像是彩色的,RGB中的这个矢量有一些解释。我会比较图像之间RGB空间中各个像素的值,看看它们是如何建模的。您可能只需要移动组件。