我正在尝试分割名片并按背景颜色拆分它们,以将它们视为不同的感兴趣区域。
例如这种卡片:
应该能够被分成两个图像,因为有 2 种背景颜色。关于如何解决这个问题有什么建议吗?我试过做一些轮廓分析,但结果不太成功。
其他示例卡:
这张卡片应该给出 3 个分段,因为即使它只有 2 种颜色也有 3 个部分(尽管 2 种颜色也可以)。
上面的卡片应该只给出一个分割,因为它只是一种背景颜色。
我还没有尝试考虑渐变背景。
这取决于其他卡的外观,但是如果图像都具有那么高的质量,那应该不会太难。
在您发布的示例中,您可以只收集边框像素的颜色(最左列、最右列、第一行、最后一行)并将您找到的内容视为可能的背景颜色。也许检查是否有足够的像素具有大致相同的颜色。你需要某种距离测量。一种简单的解决方案是仅使用 RGB 颜色空间中的欧几里得距离。
一个更通用的解决方案是在整个图像的颜色直方图中找到簇,并将具有超过总像素量 x% 的每种颜色(再次带有公差)视为背景颜色。但是您定义为背景的内容取决于您想要实现的目标以及图像的外观。
如果您需要进一步的建议,您可以发布更多图像并标记您希望将图像的哪些部分检测为背景颜色,哪些部分不是。
-
编辑:您的两个新图像也显示相同的模式。背景颜色占据了图像的很大一部分,没有噪点,也没有颜色渐变。所以一个简单的方法可能如下所示:
计算图像的直方图:参见http://docs.opencv.org/modules/imgproc/doc/histograms.html#calchist和http://docs.opencv.org/doc/tutorials/imgproc/histograms/histogram_calculation/ histogram_calculation.html
在直方图中找到最突出的颜色。如果您不想自己迭代 Mat,您可以使用 minMaxLoc ( http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#minmaxloc ),如 calchist 文档中所示(见上文),以及如果颜色占像素数的足够百分比,则保存它并将直方图中的相应 bin 设置为零。重复直到不再达到您的百分比。然后,您将保存最突出的颜色列表,即您的背景颜色。
为您拥有的每种背景颜色设置图像阈值。见:http ://docs.opencv.org/doc/tutorials/imgproc/threshold/threshold.html
在生成的 threadholded 图像上找到每种背景颜色的对应区域。见:http ://docs.opencv.org/doc/tutorials/imgproc/shapeescriptors/find_contours/find_contours.html
如果您有不适用于此方法的示例,请发布它们。
作为一种在其中也找到具有颜色渐变的背景的方法,可以使用 canny。以下代码(是的,不是 android,我知道,但如果移植它,结果应该是相同的)适用于您迄今为止发布的三个示例图像。如果您有其他不适用于此的图像,请告诉我。
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
Mat src;
Mat src_gray;
int canny_thresh = 100;
int max_canny_thresh = 255;
int size_per_mill = 120;
int max_size_per_mill = 1000;
RNG rng(12345);
bool cmp_contour_area_less(const vector<Point>& lhs, const vector<Point>& rhs)
{
return contourArea(lhs) < contourArea(rhs);
}
void Segment()
{
Mat canny_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
Canny(src_gray, canny_output, canny_thresh, canny_thresh*2, 3);
// Draw rectangle around canny image to also get regions touching the edges.
rectangle(canny_output, Point(1, 1), Point(src.cols-2, src.rows-2), Scalar(255));
namedWindow("Canny", CV_WINDOW_AUTOSIZE);
imshow("Canny", canny_output);
// Find the contours.
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
// Remove largest Contour, because it represents always the whole image.
sort(contours.begin(), contours.end(), cmp_contour_area_less);
contours.resize(contours.size()-1);
reverse(contours.begin(), contours.end());
// Maximum contour size.
int image_pixels(src.cols * src.rows);
cout << "image_pixels: " << image_pixels << "\n";
// Filter the contours, leaving just large enough ones.
vector<vector<Point> > background_contours;
for(size_t i(0); i < contours.size(); ++i)
{
double area(contourArea(contours[i]));
double min_size((size_per_mill / 1000.0) * image_pixels);
if (area >= min_size)
{
cout << "Background contour " << i << ") area: " << area << "\n";
background_contours.push_back(contours[i]);
}
}
// Draw large contours.
Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
for(size_t i(0); i < background_contours.size(); ++i)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255));
drawContours(drawing, background_contours, i, color, 1, 8, hierarchy, 0, Point());
}
namedWindow("Contours", CV_WINDOW_AUTOSIZE);
imshow("Contours", drawing);
}
void size_callback(int, void*)
{
Segment();
}
void thresh_callback(int, void*)
{
Segment();
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
cout << "Please provide an image file.\n";
return -1;
}
src = imread(argv[1]);
cvtColor(src, src_gray, CV_BGR2GRAY);
blur(src_gray, src_gray, Size(3,3));
namedWindow("Source", CV_WINDOW_AUTOSIZE);
imshow("Source", src);
if (!src.data)
{
cout << "Unable to load " << argv[1] << ".\n";
return -2;
}
createTrackbar("Canny thresh:", "Source", &canny_thresh, max_canny_thresh, thresh_callback);
createTrackbar("Size thresh:", "Source", &size_per_mill, max_size_per_mill, thresh_callback);
Segment();
waitKey(0);
}