确定阈值参数的正确方法
Proper way to determine thresholding parameters
我正在尝试实时查找三角形(蓝色轮廓)和梯形(黄色轮廓)。总的来说还可以。
但是有一些问题。首先是误报。三角形变成梯形,反之亦然。而且我不知道如何解决这个问题。
其次是 "noise"。 。我试图检查图形的面积,但噪音可以等于面积。所以它没有太大帮助。噪声取决于阈值参数。 cv::adaptiveThreshold
一点帮助也没有。它增加了更多的噪音(而且很慢)erode
和 dilate
无法以正确的方式修复它
这是我的代码。
cv::Mat detect(cv::Mat imageRGB)
{
//RGB -> GRAY
cv::Mat imageGray;
cv::cvtColor(imageRGB, imageGray, CV_BGR2GRAY);
//Bluring it
cv::Mat image;
cv::GaussianBlur(imageGray, image, cv::Size(5,5), 2);
//Thresholding
cv::threshold(image, image, 100, 255, CV_THRESH_BINARY_INV);
//SLOW and NOISE
//cv::adaptiveThreshold(image, image, 255.0, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 21, 0);
//Calculating canny params.
cv::Scalar mu;
cv::Scalar sigma;
cv::meanStdDev(image, mu, sigma);
cv::Mat imageCanny;
cv::Canny(image,
imageCanny,
mu.val[0] + sigma.val[0],
mu.val[0] - sigma.val[0]);
//Detecting conturs.
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(imageCanny, contours, hierarchy,CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
//Hierarchy is not needed here so clear it.
hierarchy.clear();
for (std::size_t i = 0; i < contours.size(); i++)
{
//fitEllipse need at last 5 points.
if (contours.at(i).size() < 5)
{
continue;
}
//Skip small contours.
if (std::fabs(cv::contourArea(contours.at(i))) < 800.0)
{
continue;
}
//Calculating RotatedRect from contours NOT from hull
//because fitEllipse need at last 5 points.
cv::RotatedRect bEllipse = cv::fitEllipse(contours.at(i));
//Finds the convex hull of a point set.
std::vector<cv::Point> hull;
cv::convexHull(contours.at(i), hull, true);
//Approx it, so we'll get 3 point for triangles
//and 4 points for trapez.
cv::approxPolyDP(hull, hull, 15, true);
//Is our contour convex. It's mast be.
if (!cv::isContourConvex(hull))
{
continue;
}
//Triangle
if (hull.size() == 3)
{
cv::drawContours(imageRGB, contours, i, cv::Scalar(255, 0, 0), 2);
cv::circle(imageRGB, bEllipse.center, 3, cv::Scalar(0, 255, 0), 2);
}
//trapez
if (hull.size() == 4)
{
cv::drawContours(imageRGB, contours, i, cv::Scalar(0, 255, 255), 2);
cv::circle(imageRGB, bEllipse.center, 3, cv::Scalar(0, 0, 255), 2);
}
}
return imageRGB;
}
所以...一般来说,所有由错误的阈值参数引起的问题,我怎样才能以正确的方式(当然是自动的)计算它?我怎样才能(哈哈,对不起我的英语)防止误报?
Thesholding - 我认为您应该尝试 Otsu 二值化 - here is some theory and a nice picture and here 是文档。这种阈值化一般是试图找到图像中最常见的 2 个值,并使用它们的平均值作为阈值。
或者考虑使用 HSV 颜色 space,可能更容易将黑白区域与其他区域区分开来。另一个想法是使用 inRange 函数(RGB 或 HSV 颜色 space - 应该在两种情况下工作) - 你需要找到 2 个范围(一个来自黑色区域,一个来自白色区域)并仅搜索这些区域(使用 inRange 函数)- 看 at this post。
完成此任务的另一种方法可能是使用某些库进行 blob 提取 like this one or blob extractor,它是 OpenCV 的一部分。
区分三角形和梯形 - 我在这里看到了两种改进解决方案的基本方法:
- 在这一行
cv::approxPolyDP(hull, hull, 15, true);
中使第三个参数(在这种情况下为15)不是一个常数值,而是等高线面积或长度的一部分。当然它应该适应轮廓大小,它不能只是一个常量值。如果不进行一些测试,很难说如何计算它 - 尝试从轮廓面积或长度的 1-5% 开始(我会从长度开始,但这只是我的猜测),看看这个值是否是 fine/to big/to small 如果需要,请检查其他值。不幸的是没有其他办法,但是手动找到这个方程应该不会花很长时间。
- 当你有 4 或 5 个点时,计算连接连续点的直线方程(点 1 和点 2,点 2 和点 3,等等不要忘记计算第一个点和最后一个点之间的线),比检查这些线中的任何 2 条是否平行(或至少接近平行 - 它们之间的角度接近 0 度) - 如果你发现任何平行线而不是这个轮廓是梯形,否则它是三角形。
我正在尝试实时查找三角形(蓝色轮廓)和梯形(黄色轮廓)。总的来说还可以。
但是有一些问题。首先是误报。三角形变成梯形,反之亦然。而且我不知道如何解决这个问题。 cv::adaptiveThreshold
一点帮助也没有。它增加了更多的噪音(而且很慢)erode
和 dilate
无法以正确的方式修复它
这是我的代码。
cv::Mat detect(cv::Mat imageRGB)
{
//RGB -> GRAY
cv::Mat imageGray;
cv::cvtColor(imageRGB, imageGray, CV_BGR2GRAY);
//Bluring it
cv::Mat image;
cv::GaussianBlur(imageGray, image, cv::Size(5,5), 2);
//Thresholding
cv::threshold(image, image, 100, 255, CV_THRESH_BINARY_INV);
//SLOW and NOISE
//cv::adaptiveThreshold(image, image, 255.0, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 21, 0);
//Calculating canny params.
cv::Scalar mu;
cv::Scalar sigma;
cv::meanStdDev(image, mu, sigma);
cv::Mat imageCanny;
cv::Canny(image,
imageCanny,
mu.val[0] + sigma.val[0],
mu.val[0] - sigma.val[0]);
//Detecting conturs.
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(imageCanny, contours, hierarchy,CV_RETR_TREE, CV_CHAIN_APPROX_NONE);
//Hierarchy is not needed here so clear it.
hierarchy.clear();
for (std::size_t i = 0; i < contours.size(); i++)
{
//fitEllipse need at last 5 points.
if (contours.at(i).size() < 5)
{
continue;
}
//Skip small contours.
if (std::fabs(cv::contourArea(contours.at(i))) < 800.0)
{
continue;
}
//Calculating RotatedRect from contours NOT from hull
//because fitEllipse need at last 5 points.
cv::RotatedRect bEllipse = cv::fitEllipse(contours.at(i));
//Finds the convex hull of a point set.
std::vector<cv::Point> hull;
cv::convexHull(contours.at(i), hull, true);
//Approx it, so we'll get 3 point for triangles
//and 4 points for trapez.
cv::approxPolyDP(hull, hull, 15, true);
//Is our contour convex. It's mast be.
if (!cv::isContourConvex(hull))
{
continue;
}
//Triangle
if (hull.size() == 3)
{
cv::drawContours(imageRGB, contours, i, cv::Scalar(255, 0, 0), 2);
cv::circle(imageRGB, bEllipse.center, 3, cv::Scalar(0, 255, 0), 2);
}
//trapez
if (hull.size() == 4)
{
cv::drawContours(imageRGB, contours, i, cv::Scalar(0, 255, 255), 2);
cv::circle(imageRGB, bEllipse.center, 3, cv::Scalar(0, 0, 255), 2);
}
}
return imageRGB;
}
所以...一般来说,所有由错误的阈值参数引起的问题,我怎样才能以正确的方式(当然是自动的)计算它?我怎样才能(哈哈,对不起我的英语)防止误报?
Thesholding - 我认为您应该尝试 Otsu 二值化 - here is some theory and a nice picture and here 是文档。这种阈值化一般是试图找到图像中最常见的 2 个值,并使用它们的平均值作为阈值。
或者考虑使用 HSV 颜色 space,可能更容易将黑白区域与其他区域区分开来。另一个想法是使用 inRange 函数(RGB 或 HSV 颜色 space - 应该在两种情况下工作) - 你需要找到 2 个范围(一个来自黑色区域,一个来自白色区域)并仅搜索这些区域(使用 inRange 函数)- 看 at this post。
完成此任务的另一种方法可能是使用某些库进行 blob 提取 like this one or blob extractor,它是 OpenCV 的一部分。
区分三角形和梯形 - 我在这里看到了两种改进解决方案的基本方法:
- 在这一行
cv::approxPolyDP(hull, hull, 15, true);
中使第三个参数(在这种情况下为15)不是一个常数值,而是等高线面积或长度的一部分。当然它应该适应轮廓大小,它不能只是一个常量值。如果不进行一些测试,很难说如何计算它 - 尝试从轮廓面积或长度的 1-5% 开始(我会从长度开始,但这只是我的猜测),看看这个值是否是 fine/to big/to small 如果需要,请检查其他值。不幸的是没有其他办法,但是手动找到这个方程应该不会花很长时间。 - 当你有 4 或 5 个点时,计算连接连续点的直线方程(点 1 和点 2,点 2 和点 3,等等不要忘记计算第一个点和最后一个点之间的线),比检查这些线中的任何 2 条是否平行(或至少接近平行 - 它们之间的角度接近 0 度) - 如果你发现任何平行线而不是这个轮廓是梯形,否则它是三角形。