OpenCV锐化边缘(没有孔的边缘)
OpenCV sharpen the edges (edges with no holes)
我正在尝试检测 biggest/larger 矩形并将边界框绘制到检测到的区域。
在我的用例中,代表矩形形状的对象经常(但不总是)是白色的,背景的颜色也与白色非常相似。
在检测轮廓之前,我对图像进行了预处理以检测完美的边缘。
我的问题是我无法完美地检测到边缘,即使在模糊和使用 'adaptive threshold' 或 'threshold'.
之后我也有很多噪音
The original image i have used for contours detection
我尝试了不同的方法来检测不同光照条件下的完美边缘,但没有成功。
如何处理图像以检测轮廓检测的完美边缘(没有孔的边缘)?
下面是我使用的代码
public static Mat findRectangleX(Mat original) {
Mat src = original.clone();
Mat gray = new Mat();
Mat binary = new Mat();
MatOfPoint2f approxCurve;
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
if (original.type() != CvType.CV_8U) {
Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY);
} else {
original.copyTo(gray);
}
Imgproc.GaussianBlur(gray, gray, new Size(5,5),0);
Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1);
//Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
double maxArea = 0;
Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i<contours.size();i++) {
MatOfPoint contour = contours.get(i);
MatOfPoint2f temp = new MatOfPoint2f(contour.toArray());
double area = Imgproc.contourArea(contour);
approxCurve = new MatOfPoint2f();
Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true);
if (approxCurve.total() == 4 ) {
Rect rect = Imgproc.boundingRect(contours.get(i));
Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4);
if(maxArea < area)
maxArea = area;
}
}
Log.v(TAG, "Total contours found : " + contours.size());
Log.v(TAG, "Max area :" + maxArea);
return src;
}
我在 Whosebug 上搜索了类似的问题并尝试了代码示例,但它们中的任何一个都对我有用。我觉得难点是白色背景上的白色物体。
如何处理图像以锐化轮廓检测的边缘?
如何检测 biggest/larger 矩形并将矩形线绘制到检测到的形状?
//更新时间:20/02/2017
我已经尝试了@Nejc 在下面 post 中建议的解决方案。分割效果更好,但我的轮廓仍然有漏洞,并且 findcontours 无法检测到较大的轮廓。
下面是@Nejc 提供并翻译成 java.
的代码
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat abs_x = new Mat();
Core.convertScaleAbs(sobx,abs_x);
Mat abs_y = new Mat();
Core.convertScaleAbs(soby,abs_y);
Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx);
sobx.release();
soby.release();
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
return thresholded;
}
这是我在 运行 上面的代码
之后得到的图像
Image after using @Nejc solution
1) 为什么我的翻译后的代码输出的图像与@Nejc 不一样?
应用于同一图像的相同代码应产生相同的输出?
2) 我翻译的时候是不是遗漏了什么?
3) 根据我的理解,为什么我们在这条指令中将图像乘以自身 sobx = sobx.mul(sobx); ?
我通过计算输入图像的梯度绝对值的近似值,设法获得了一张非常漂亮的边缘图像。
编辑: 在开始工作之前,我将输入图像的尺寸缩小了 5 倍。 Click here to see it!。如果你在那个图像上使用我的代码,结果会很好。如果你想让我的代码很好地处理原始尺寸的图像,那么:
- 将高斯核大小和西格玛乘以 5,或
- 按因子 5 对图像进行下采样,执行算法,然后按因子 5 对结果进行上采样(这应该比第一个选项快得多)
这是我得到的结果:
我的程序依赖于两个关键特性。第一个是转换为适当的颜色 space。 As Jeru Luke stated in his answer ,HSV 颜色 space 中的饱和度通道是这里不错的选择。第二个重要的事情是梯度绝对值的估计。为此,我使用了 sobel 运算符和一些算术。如果有人要求,我可以提供额外的解释。
这是我用来获取第一张图片的代码。
using namespace std;
using namespace cv;
Mat img_rgb = imread("letter.jpg");
Mat img_hsv;
cvtColor(img_rgb, img_hsv, CV_BGR2HSV);
vector<Mat> channels_hsv;
split(img_hsv, channels_hsv);
Mat channel_s = channels_hsv[1];
GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2);
Mat imf;
channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Mat sobx, soby;
Sobel(imf, sobx, -1, 1, 0);
Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat grad_abs_val_approx;
cv::pow(sobx + soby, 0.5, grad_abs_val_approx);
Mat filtered;
GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2);
Scalar mean, stdev;
meanStdDev(filtered, mean, stdev);
Mat thresholded;
cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO);
// I scale the image at this point so that it is displayed properly
imshow("image", thresholded/50);
这就是我计算第二张图片的方式:
Mat thresholded_bin;
cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY);
Mat converted;
thresholded_bin.convertTo(converted, CV_8UC1);
vector<vector<Point>> contours;
findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
Mat contour_img = Mat::zeros(converted.size(), CV_8UC1);
drawContours(contour_img, contours, -1, 255);
imshow("contours", contour_img);
感谢您的意见和建议。
@NEJC 提供的代码完美运行,涵盖了我 80% 的用例。
然而,它不适用于类似这样的情况
case not solved by the current code
我不知道为什么。
也许有人 idea/clue/solution ?
我继续改进代码并尝试找到一个更通用的解决方案,可以涵盖更多案例。如果我找到了,我会 post 它。
无论如何,下面是基于@NEJC 解决方案和注释的工作代码。
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat sumxy = new Mat();
Core.add(sobx,soby, sumxy);
Core.pow(sumxy, 0.5, grad_abs_val_approx);
sobx.release();
soby.release();
sumxy.release();;
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
Mat converted = new Mat();
thresholded.convertTo(converted, CV_8UC1);
return converted;
}
我正在尝试检测 biggest/larger 矩形并将边界框绘制到检测到的区域。 在我的用例中,代表矩形形状的对象经常(但不总是)是白色的,背景的颜色也与白色非常相似。
在检测轮廓之前,我对图像进行了预处理以检测完美的边缘。 我的问题是我无法完美地检测到边缘,即使在模糊和使用 'adaptive threshold' 或 'threshold'.
之后我也有很多噪音The original image i have used for contours detection
我尝试了不同的方法来检测不同光照条件下的完美边缘,但没有成功。
如何处理图像以检测轮廓检测的完美边缘(没有孔的边缘)?
下面是我使用的代码
public static Mat findRectangleX(Mat original) {
Mat src = original.clone();
Mat gray = new Mat();
Mat binary = new Mat();
MatOfPoint2f approxCurve;
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
if (original.type() != CvType.CV_8U) {
Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY);
} else {
original.copyTo(gray);
}
Imgproc.GaussianBlur(gray, gray, new Size(5,5),0);
Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1);
//Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
double maxArea = 0;
Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i<contours.size();i++) {
MatOfPoint contour = contours.get(i);
MatOfPoint2f temp = new MatOfPoint2f(contour.toArray());
double area = Imgproc.contourArea(contour);
approxCurve = new MatOfPoint2f();
Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true);
if (approxCurve.total() == 4 ) {
Rect rect = Imgproc.boundingRect(contours.get(i));
Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4);
if(maxArea < area)
maxArea = area;
}
}
Log.v(TAG, "Total contours found : " + contours.size());
Log.v(TAG, "Max area :" + maxArea);
return src;
}
我在 Whosebug 上搜索了类似的问题并尝试了代码示例,但它们中的任何一个都对我有用。我觉得难点是白色背景上的白色物体。
如何处理图像以锐化轮廓检测的边缘?
如何检测 biggest/larger 矩形并将矩形线绘制到检测到的形状?
//更新时间:20/02/2017
我已经尝试了@Nejc 在下面 post 中建议的解决方案。分割效果更好,但我的轮廓仍然有漏洞,并且 findcontours 无法检测到较大的轮廓。 下面是@Nejc 提供并翻译成 java.
的代码public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat abs_x = new Mat();
Core.convertScaleAbs(sobx,abs_x);
Mat abs_y = new Mat();
Core.convertScaleAbs(soby,abs_y);
Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx);
sobx.release();
soby.release();
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
return thresholded;
}
这是我在 运行 上面的代码
之后得到的图像Image after using @Nejc solution
1) 为什么我的翻译后的代码输出的图像与@Nejc 不一样? 应用于同一图像的相同代码应产生相同的输出?
2) 我翻译的时候是不是遗漏了什么?
3) 根据我的理解,为什么我们在这条指令中将图像乘以自身 sobx = sobx.mul(sobx); ?
我通过计算输入图像的梯度绝对值的近似值,设法获得了一张非常漂亮的边缘图像。
编辑: 在开始工作之前,我将输入图像的尺寸缩小了 5 倍。 Click here to see it!。如果你在那个图像上使用我的代码,结果会很好。如果你想让我的代码很好地处理原始尺寸的图像,那么:
- 将高斯核大小和西格玛乘以 5,或
- 按因子 5 对图像进行下采样,执行算法,然后按因子 5 对结果进行上采样(这应该比第一个选项快得多)
这是我得到的结果:
我的程序依赖于两个关键特性。第一个是转换为适当的颜色 space。 As Jeru Luke stated in his answer ,HSV 颜色 space 中的饱和度通道是这里不错的选择。第二个重要的事情是梯度绝对值的估计。为此,我使用了 sobel 运算符和一些算术。如果有人要求,我可以提供额外的解释。
这是我用来获取第一张图片的代码。
using namespace std;
using namespace cv;
Mat img_rgb = imread("letter.jpg");
Mat img_hsv;
cvtColor(img_rgb, img_hsv, CV_BGR2HSV);
vector<Mat> channels_hsv;
split(img_hsv, channels_hsv);
Mat channel_s = channels_hsv[1];
GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2);
Mat imf;
channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Mat sobx, soby;
Sobel(imf, sobx, -1, 1, 0);
Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat grad_abs_val_approx;
cv::pow(sobx + soby, 0.5, grad_abs_val_approx);
Mat filtered;
GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2);
Scalar mean, stdev;
meanStdDev(filtered, mean, stdev);
Mat thresholded;
cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO);
// I scale the image at this point so that it is displayed properly
imshow("image", thresholded/50);
这就是我计算第二张图片的方式:
Mat thresholded_bin;
cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY);
Mat converted;
thresholded_bin.convertTo(converted, CV_8UC1);
vector<vector<Point>> contours;
findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
Mat contour_img = Mat::zeros(converted.size(), CV_8UC1);
drawContours(contour_img, contours, -1, 255);
imshow("contours", contour_img);
感谢您的意见和建议。 @NEJC 提供的代码完美运行,涵盖了我 80% 的用例。
然而,它不适用于类似这样的情况 case not solved by the current code 我不知道为什么。
也许有人 idea/clue/solution ?
我继续改进代码并尝试找到一个更通用的解决方案,可以涵盖更多案例。如果我找到了,我会 post 它。
无论如何,下面是基于@NEJC 解决方案和注释的工作代码。
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat sumxy = new Mat();
Core.add(sobx,soby, sumxy);
Core.pow(sumxy, 0.5, grad_abs_val_approx);
sobx.release();
soby.release();
sumxy.release();;
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
Mat converted = new Mat();
thresholded.convertTo(converted, CV_8UC1);
return converted;
}