一般信息
- 我正在Windows 10上使用OpenCV 3.2.0,但是所有提到的功能都应该在2.4和Android上可用。
- 为了更好的可视化,我已经调整了图像大小。这不会影响当前解决问题的方法,
但是如果我们要使用某种边缘检测,则绝对应该使用原始图像大小。
- 当前提供的解决方案使用了大量自定义功能(LAB颜色检测、轮廓大小分析等),这些功能无法在此公开。如果您需要特定区域的帮助,当然可以在评论中请求帮助。
问题的一般观察
有几个原因导致您之前的方法没有奏效。
在我们介绍解决方案之前,这里有一些需要考虑的观察结果:
- 对象包含与背景相比较暗和亮的元素。
- 对象由相对于亮度和颜色而言相当不同的部分组成,以及普遍的均匀性。 实际上,该对象被一个看起来很像背景的部分分隔开来。
- 您有明显区别于一般背景的背景对象(例如右上角的黑色对象)。
- 该对象经常被以略微倾斜的角度捕捉。这会导致在否则是矩形对象的透视变换。
解决方案
考虑到上述观察结果,我认为简单的阈值处理或边缘检测不会产生可靠的结果,特别是当考虑同一场景的不同图像之间的变化时。
作为解决方案,我建议通过LAB或HSV颜色空间进行前景和/或背景颜色检测和分类。
应使用样本图像来分类各个区域。例如,对于前景,应使用书的暗红色和亮红色以及金色/黄色作为样本颜色。背景由一个相当均匀的灰色组成,可以用于其检测。
潜在的算法:
- 根据LAB颜色空间检测和分类前景和背景。使用合理的颜色距离阈值(对我来说,LAB空间中约为8-10%的阈值效果很好- AB空间可能适用于5-7%)。如果由于变化的亮度而导致颜色变化成为问题,则切换到无亮度依赖的方法(例如仅使用AB分量并忽略L分量)
- 从前景检测中排除背景的部分(分类可能会有重叠,因此这种顺序将防止混淆)。
- 在剩余的二进制图像上应用轮廓搜索,并丢弃面积过小的轮廓。
- 剩下的轮廓形成了书。创建一个凸包,您可以将其用作对象ROI。
优点:
- 非常准确
- 适用于多种情况(背景变化,不同的照明-如果使用正确的颜色空间)
缺点:
- 对于初学者来说,实现难度较大(需要了解LAB或HSV、颜色距离、支持多颜色分类等)。
- 颜色检测完全依赖于背景和前景。这意味着如果书本发生变化,例如变成蓝色,则需要调整示例图像。
- 如果书本的顶部、底部或侧面都与背景相似,则此方法将无法起作用。在这种情况下,这些部分将被归类为背景。
通用解决方案的难度
目前的方法虽然先进,但也有适用范围限制(不同的书籍、不同的背景等)。
如果你想要一个通用系统,可以自动检测不同背景中的不同书籍,那么你会遇到一些麻烦。
这达到了一个难度水平,很难解决。这有点让我想起了车牌的识别:
不同的光照、噪声、污渍物体、强烈变化的背景、对比度差等等。
即使你成功了,还有个问题:这样的系统只能用于特定类型的车牌。
同样适用于你的书籍。
测试
由于你发布了一个非常相似的问题(在OpenCV4Android中检测多颜色文档),我有些自作主张地使用了那里发布的图像以及你在这里提供的图像。
由于其中一张图片仅带有红色ROI,所以我使用了我的Photoshop技能水平> 9000来删除了红色ROI :)。
用于背景分类的示例图像
![b](https://istack.dev59.com/7MJO1.webp)
用于前景分类的示例图像
![4](https://istack.dev59.com/wV90U.webp)
图像
![7](https://istack.dev59.com/29Bly.webp)
背景分类
![10](https://istack.dev59.com/QTyX2.webp)
前景分类
![13](https://istack.dev59.com/9c0EI.webp)
检测到的对象
![10](https://istack.dev59.com/np3lX.webp)
更新
快速LAB崩溃课程
由于色彩空间的理论非常广泛,所以您应该首先了解一些基础知识和关键点。
我的快速搜索发现了这个网站,它很好地解释了一些重要的点:http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/
- 我们将使用OpenCV的浮点变量,因为它是最简单的(未改变的LAB范围,无缩放,无移位等)。
- LAB值的范围:
L *轴(亮度)从0到100
a *和b *(颜色属性)轴范围从-128到+ 127
来源和参考资料:
CIELAB颜色空间的坐标范围是什么?
http://www.colourphil.co.uk/lab_lch_colour_space.shtml
颜色距离
https://en.wikipedia.org/wiki/Color_difference
本质上,我们使用两种颜色之间的欧几里得距离。
当然,我们可以省略比较的两种颜色中的某些组件,例如亮度分量(L)。
为了获得直观的颜色距离度量,我们可以将颜色距离归一化为0.0到1.0的范围。
这样,我们可以将颜色距离解释为百分数偏差。
例子
让我们使用上面发布的教程页面中的图像并在示例中使用它们。
示例应用程序显示以下内容:
- BGR到LAB转换
- (L)AB距离计算
- (L)AB距离归一化
- 根据BGR / LAB值和颜色距离阈值对颜色进行分类
- 物体的颜色如何在不同的照明条件下发生变化
- 随着图像变得更暗/更亮,与其他颜色的距离可能会变大/更接近(如果仔细阅读发布的链接,这也变得清晰)。
额外提示:
示例应该表明,在强烈变化的照明条件下,单一颜色通常不足以检测彩色物体。
一种解决方法是通过经验分析为每种颜色使用不同的颜色距离阈值。
另一种选择是为要查找的每种颜色使用许多分类样本颜色。您需要计算到每个这些样本颜色的颜色距离,并将找到的值通过OR运算符合并结果。
代码和图片
![18](https://istack.dev59.com/T16LT.webp)
(图片来源于http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/
- Satya Mallick的教程)
#include <opencv2/opencv.hpp>
static const float labNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(100, 2) + std::pow(255, 2) + std::pow(255, 2))));
static const float abNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(255, 2) + std::pow(255, 2))));
float labExample_calculateLabDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
return (float)cv::norm(c1, c2) * labNormalizationFactor;
}
float labExample_calculateAbDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
cv::Vec2f c1Temp(c1(1), c1(2));
cv::Vec2f c2Temp(c2(1), c2(2));
return (float)cv::norm(c1Temp, c2Temp) * abNormalizationFactor;
}
void labExample_calculateLabDistance(
cv::Mat& imgLabFloat,
cv::Mat& distances,
const cv::Vec3f labColor,
const bool useOnlyAbDistance
)
{
const auto& size = imgLabFloat.size();
distances = cv::Mat::zeros(size, CV_32F);
distances = 1.f;
for (int y = 0; y < size.height; ++y)
{
for (int x = 0; x < size.width; ++x)
{
const auto& value = imgLabFloat.at<cv::Vec3f>(y,x);
float distanceValue;
if (useOnlyAbDistance)
{
distanceValue = labExample_calculateAbDistance(value, labColor);
}
else
{
distanceValue = labExample_calculateLabDistance(value, labColor);
}
distances.at<float>(y,x) = distanceValue;
}
}
}
cv::Vec3f labExample_bgrUchar2LabFloat(const cv::Scalar bgr)
{
cv::Mat matWithSinglePixel = cv::Mat::zeros(1, 1, CV_8UC3);
matWithSinglePixel.setTo(bgr);
matWithSinglePixel.convertTo(matWithSinglePixel, CV_32FC3, 1.0 / 255.0);
cv::cvtColor(matWithSinglePixel, matWithSinglePixel, CV_BGR2Lab);
auto retval = matWithSinglePixel.at<cv::Vec3f>(0, 0);
return retval;
}
void labExample_convertImageBgrUcharToLabFloat(cv::Mat& src, cv::Mat& dst)
{
src.convertTo(dst, CV_32FC3, 1.0 / 255.0);
cv::cvtColor(dst, dst, CV_BGR2Lab);
}
void labExample()
{
std::string path = "./Testdata/Stackoverflow lab example/";
std::string filename1 = "1.jpg";
std::string fqn1 = path + filename1;
cv::Mat img1 = cv::imread(fqn1, cv::IMREAD_COLOR);
std::string filename2 = "2.jpg";
std::string fqn2 = path + filename2;
cv::Mat img2 = cv::imread(fqn2, cv::IMREAD_COLOR);
float scalingFactorX = (float)img1.cols / img2.cols;
float scalingFactorY = scalingFactorX;
cv::resize(img2, img2, cv::Size(), scalingFactorX, scalingFactorY);
std::vector<cv::Mat> mats;
mats.push_back(img1);
mats.push_back(img2);
cv::Mat img;
cv::vconcat(mats, img);
cv::Scalar bgrColorRed(52, 42, 172);
cv::Scalar bgrColorOrange(3, 111, 219);
cv::Scalar bgrColorYellow(1, 213, 224);
cv::Scalar bgrColorBlue(187, 95, 0);
cv::Scalar bgrColorGray(127, 127, 127);
cv::Mat imgLabFloat;
labExample_convertImageBgrUcharToLabFloat(img, imgLabFloat);
auto colorLabFloat = labExample_bgrUchar2LabFloat(bgrColorRed);
cv::Mat colorDistancesWithL;
cv::Mat colorDistancesWithoutL;
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithL, colorLabFloat, false);
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithoutL, colorLabFloat, true);
float maxColorDistanceWithL = 0.07f;
float maxColorDistanceWithoutL = 0.07f;
cv::Mat detectedValuesWithL = colorDistancesWithL <= maxColorDistanceWithL;
cv::Mat detectedValuesWithoutL = colorDistancesWithoutL <= maxColorDistanceWithoutL;
cv::Mat imgWithDetectedValuesWithL = cv::Mat::zeros(img.size(), CV_8UC3);
cv::Mat imgWithDetectedValuesWithoutL = cv::Mat::zeros(img.size(), CV_8UC3);
img.copyTo(imgWithDetectedValuesWithL, detectedValuesWithL);
img.copyTo(imgWithDetectedValuesWithoutL, detectedValuesWithoutL);
cv::imshow("img", img);
cv::imshow("colorDistancesWithL", colorDistancesWithL);
cv::imshow("colorDistancesWithoutL", colorDistancesWithoutL);
cv::imshow("detectedValuesWithL", detectedValuesWithL);
cv::imshow("detectedValuesWithoutL", detectedValuesWithoutL);
cv::imshow("imgWithDetectedValuesWithL", imgWithDetectedValuesWithL);
cv::imshow("imgWithDetectedValuesWithoutL", imgWithDetectedValuesWithoutL);
cv::waitKey();
}
int main(int argc, char** argv)
{
labExample();
}