一般信息
- 我在 Windows 10 上使用 OpenCV 3.2.0,但是所有提到的功能都应该在 2.4 和/或 Android 中可用。
- 我调整了图像的大小以获得更好的可视化效果。这不会影响当前解决问题的方法,但是如果我们要使用某种边缘检测,我们绝对应该使用原始图像大小。
- 当前提供的解决方案使用了很多自定义功能(LAB 颜色检测、轮廓尺寸分析等),此处无法实现。如果您在特定领域需要帮助,当然可以在评论中寻求帮助。
问题的一般观察
您以前的方法不起作用有几个原因。在我们找到解决方案之前,这里有一些需要考虑的观察结果:
- 与背景相比,您有一个包含更暗和更亮元素的对象。
- 您有一个对象,它由关于亮度和颜色以及一般同质性的相当不同的部分组成。事实上,对象被一个看起来很像背景的部分分割。
- 您的背景对象与一般背景有明显的区别(例如右上角的黑色对象)。
- 物体通常是从稍微倾斜的角度拍摄的。这会导致矩形对象的透视变换。
解决方案
考虑到上述观察结果,我认为简单的阈值处理或边缘检测不会产生任何可靠的结果,尤其是在查看同一场景的不同图像之间的变化时。作为一种解决方案,我建议通过 LAB 或 HSV 颜色空间进行前景和/或背景颜色检测和分类。应使用最突出颜色的样本图像对各个区域进行分类。例如,对于前景,书的深红色和鲜红色以及金色/黄色。背景由相当均匀的浅灰色组成,可用于其检测。潜在算法:
- 根据 LAB 颜色空间检测和分类前景和背景。使用合理的颜色距离阈值(对我来说,大约 8-10% 在 LAB 空间中工作 - AB 空间可能在 5-7% 中工作)。如果由于亮度变化导致的颜色变化成为问题,则切换到与亮度无关的方法(例如,只使用 AB 分量并忽略 L 分量)
- 从前景检测中排除部分背景(分类中可能存在一些重叠,因此此顺序将防止混淆)。
- 在剩余的二值图像上,应用轮廓搜索并丢弃面积过小的轮廓。
- 其余的轮廓形成了书。创建一个可以用作对象 ROI 的凸包。
优点:
- 非常精准
- 适用于多种场景(改变背景、不同的照明——如果使用了正确的色彩空间)
缺点:
- 初学者难以实现(LAB 或 HSV 知识、颜色距离、支持多颜色分类等)
- 颜色检测完全依赖于背景和前景。这意味着如果书发生变化并且例如是蓝色的,则必须调整示例图像。
- 如果书的所有顶部、底部或侧面看起来都像背景,那么这种方法将不起作用。如果是这样,本书的这些部分将被归类为背景。
一般解决方案的难度
当前的方法虽然先进,但对于一般应用(不同的书籍、不同的背景等)来说还是不够的,这是有原因的。
如果你想要一个通用系统,它可以自动检测不同背景下的不同书籍,你就会遇到一些麻烦。这达到了难以解决的难度。这让我想起了车牌检测:变化的照明、噪音、染色的物体、强烈变化的背景、差的对比度等。即使你能做到这一点,这里有个问题:这样的系统只适用于特定类型车牌。这同样适用于你的书。
测试
由于您发布了一个非常相似的问题(使用 OpenCV4Android 检测多色文档),我冒昧地使用了发布在那里的图像以及您在此处提供的图像。由于其中一张图像仅适用于红色 ROI,因此我使用我的 Photoshop 技能水平 > 9000 来删除红色 ROI :)。
背景分类的样本图像
前景分类的样本图像
图片
背景分类
前景分类
检测到的物体
更新
快速实验室速成课程
由于色彩空间的理论非常广泛,您应该首先阅读一些基础知识和关键点。我的快速搜索发现这个网站很好地解释了一些要点: 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 值和颜色距离阈值进行颜色分类 - 物体颜色在不同光照下如何变化条件 - 与其他颜色的距离如何变大/接近图像变得更暗/更亮(如果您仔细阅读发布的链接,这也会变得清晰)。
附加提示:该示例应表明,单一颜色通常不足以在强烈变化的照明条件下检测颜色对象。一种解决方案是通过经验分析为每种颜色使用不同的颜色距离阈值。另一种方法是为您要查找的每种颜色使用许多分类样本颜色。您必须计算与这些样本颜色中的每一种颜色的颜色距离,并通过对结果进行 ORing 来组合找到的值。
代码和图像
(图片取自http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/
- Satya Mallick 的教程)
#include <opencv2/opencv.hpp>
// Normalization factors for (L)AB distance calculation
// LAB range:
// L: 0.0 - 100.0
// A: -128.0 - 127.0
// B: -128.0 - 127.0
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
)
{
// Get size for general usage
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)
{
// Read LAB value
const auto& value = imgLabFloat.at<cv::Vec3f>(y,x);
// Calculate distance
float distanceValue;
if (useOnlyAbDistance)
{
distanceValue = labExample_calculateAbDistance(value, labColor);
}
else
{
distanceValue = labExample_calculateLabDistance(value, labColor);
}
distances.at<float>(y,x) = distanceValue;
}
}
}
// Small hacky function to convert a single
// BGR color value to LAB float.
// Since the conversion function is not directly available
// we just use a Mat object to do the conversion.
cv::Vec3f labExample_bgrUchar2LabFloat(const cv::Scalar bgr)
{
// Build Mat with single bgr pixel
cv::Mat matWithSinglePixel = cv::Mat::zeros(1, 1, CV_8UC3);
matWithSinglePixel.setTo(bgr);
// Convert to float and scale accordingly
matWithSinglePixel.convertTo(matWithSinglePixel, CV_32FC3, 1.0 / 255.0);
// Convert to LAB and return value
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()
{
// Load image
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);
// Combine images by scaling the second image so both images have the same number of columns and then combining them.
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);
// Lets use some reference colors.
// Remember: OpenCV uses BGR as default color space so all colors
// are BGR by default, too.
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);
// Build LAB image
cv::Mat imgLabFloat;
labExample_convertImageBgrUcharToLabFloat(img, imgLabFloat);
// Convert bgr ref color to lab float.
// INSERT color you want to analyze here:
auto colorLabFloat = labExample_bgrUchar2LabFloat(bgrColorRed);
cv::Mat colorDistancesWithL;
cv::Mat colorDistancesWithoutL;
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithL, colorLabFloat, false);
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithoutL, colorLabFloat, true);
// Color distances. They can differ for every color being analyzed.
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();
}