在 OpenCV 3.3 中查找具有复杂背景和丰富纹理的图像中的轮廓
Find contours in images with complex background and rich texture in OpenCV 3.3
我正在开展一个项目,使用 OpenCV 3.3 处理如下所示的大理石板图像。
可以在 https://1drv.ms/f/s!AjoScZ1lKToFheM6wmamv45R7zHwaQ
上找到我正在处理的具有不同大理石纹理和尺寸的更多样本
要求是:
- 将大理石板与背景分开并移除背景(填充白色),因此只显示大理石板。
- 计算石板的面积(相机到石板的距离和镜头的参数已知)
我使用的策略是:1) 找到大理石板的轮廓,2) 去除不在轮廓内的部分,3) 得到轮廓的面积大小,4) 计算它的物理面积。
板坯的轮廓如下图红色部分所示(这是手工完成的)。
我尝试了几种方法来寻找图像中石板的轮廓,但由于复杂的背景和丰富的大理石纹理,未能获得令人满意的结果。
我使用的处理逻辑是:将图片转灰化模糊,用Canny求边缘,再用findContours求轮廓,代码如下:
Mat img = imread('test2.jpg', 1);
Mat gray, edge;
cvtColor(img, gray, COLOR_BGR2GRAY);
blur(gray, gray, Size(3, 3));
Canny(gray, edge, 50, 200, 3);
vector<vector<Point> > contours;
vector<Vec4i> lines;
findContours(edge, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
cout << "Number of contours detected: " << contours.size() << endl;
vector<Vec4i> hierarchy;
for (int i = 0; i < contours.size(); i++)
{
drawContours(img, contours, i, Scalar(0, 0, 255), 1, 8, hierarchy, 0, Point());
}
imshow("Output", img);
我尝试为数十种组合调整模糊和 Canny 参数,但仍然失败。我也试过用HoughLinesP找几组不同参数的slab边缘,也没有成功。
我是计算机视觉的新手,现在遇到的问题是:
- 我是否正在以错误的方式或策略寻找板坯轮廓?有没有更好的算法或组合?还是我需要专注于调整 Canny/findContours/HoughLinesP 算法的参数?
- 这种图片真的因为背景复杂很难处理吗?
我乐于接受任何可能帮助我完成目标的建议。提前谢谢你。
您可以考虑的技术
- 模板匹配,不同的可能需要准备很多模板
大理石(光线条件、旋转等)
- 训练分类器+区域建议,只有在其他解决方案失败时才采用此解决方案(此解决方案可能是最稳健的,但也是实施起来最冗长的)
由于您只有 10~20 种大理石板,我认为解决方案 1 是一个好的开始。
手动找出大理石的4个角点,做透视变换
pair<Mat, vector<Point2f>> get_target_marble(Mat const &input, vector<Point2f> const &src_pts)
{
using namespace cv;
using namespace std;
Point2f const tl = src_pts[0];
Point2f const tr = src_pts[1];
Point2f const br = src_pts[2];
Point2f const bl = src_pts[3];
auto const euclidean_dist = [](Point const &a, Point const &b)
{
return std::sqrt(std::pow(a.x-b.x, 2) + std::pow(a.y - b.y, 2));
};
int const max_width = static_cast<int>(std::max(euclidean_dist(br, bl), euclidean_dist(tr, tl)));
int const max_height = static_cast<int>(std::max(euclidean_dist(tr, br), euclidean_dist(tl, bl)));
vector<Point2f> const src{tl, tr, br, bl};
vector<Point2f> dst{Point(0,0), Point(max_width -1,0), Point(max_width-1,max_height-1), Point(0,max_height-1)};
Mat const hmat = getPerspectiveTransform(src, dst);
Mat target;
warpPerspective(input, target, hmat, {max_width, max_height});
return std::make_pair(std::move(target), std::move(dst));
}
找出查询图像(大理石板)和训练图像(图像可能包含大理石板)之间的单应矩阵
Mat find_homography(Mat const &train, Mat const &query)
{
Ptr<AKAZE> akaze = AKAZE::create();
vector<KeyPoint> query_kpts, train_kpts;
cv::Mat query_desc, train_desc;
akaze->detectAndCompute(train, cv::noArray(), query_kpts, query_desc);
akaze->detectAndCompute(query, cv::noArray(), train_kpts, train_desc);
BFMatcher matcher(NORM_HAMMING);
vector<vector<DMatch>> nn_matches;
//top 2 matches because we need to apply David Lowe's ratio test
matcher.knnMatch(train_desc, query_desc, nn_matches, 2);
vector<KeyPoint> matches1, matches2;
for(auto const &m : nn_matches){
float const dist1 = m[0].distance;
float const dist2 = m[1].distance;
if(dist1 < 0.7 * dist2){
matches1.emplace_back(train_kpts[m[0].queryIdx]);
matches2.emplace_back(query_kpts[m[0].trainIdx]);
}
}
if(matches1.size() > 4){
std::vector<cv::Point2f> points1, points2;
for(size_t i = 0; i != matches1.size(); ++i){
points1.emplace_back(matches1[i].pt);
points2.emplace_back(matches2[i].pt);
}
return cv::findHomography(points1, points2, cv::RANSAC, 5.0);
}
return {};
}
将4行查询图像映射到目标图像
vector<Point2f> query_points;
vector<Point> qpts;
perspectiveTransform(dst, query_points, hmat);
for(auto const &pt : query_points){
cout<<pt<<endl;
qpts.emplace_back(pt);
}
polylines(input, qpts, true, {255,0,0}, 2);
你需要为这个解决方案准备10~20张图片,更喜欢那个
存在大多数匹配点以定位您的大理石板。如果
性能是一个问题,降低图像的分辨率,你不
需要一张大图才能得到结果。
完整代码放在github。
ps : 我不知道你的项目细节,如果只有10~20种大理石板+它们都有很好的特征可以追踪,你不需要3个月来解决它(但你可以告诉你的 bosses/customers 你需要 3 个月 :),有时更好的表现只会导致更多的家务活,而不是更多的钱)。
我正在开展一个项目,使用 OpenCV 3.3 处理如下所示的大理石板图像。
可以在 https://1drv.ms/f/s!AjoScZ1lKToFheM6wmamv45R7zHwaQ
上找到我正在处理的具有不同大理石纹理和尺寸的更多样本要求是:
- 将大理石板与背景分开并移除背景(填充白色),因此只显示大理石板。
- 计算石板的面积(相机到石板的距离和镜头的参数已知)
我使用的策略是:1) 找到大理石板的轮廓,2) 去除不在轮廓内的部分,3) 得到轮廓的面积大小,4) 计算它的物理面积。
板坯的轮廓如下图红色部分所示(这是手工完成的)。
我尝试了几种方法来寻找图像中石板的轮廓,但由于复杂的背景和丰富的大理石纹理,未能获得令人满意的结果。
我使用的处理逻辑是:将图片转灰化模糊,用Canny求边缘,再用findContours求轮廓,代码如下:
Mat img = imread('test2.jpg', 1);
Mat gray, edge;
cvtColor(img, gray, COLOR_BGR2GRAY);
blur(gray, gray, Size(3, 3));
Canny(gray, edge, 50, 200, 3);
vector<vector<Point> > contours;
vector<Vec4i> lines;
findContours(edge, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
cout << "Number of contours detected: " << contours.size() << endl;
vector<Vec4i> hierarchy;
for (int i = 0; i < contours.size(); i++)
{
drawContours(img, contours, i, Scalar(0, 0, 255), 1, 8, hierarchy, 0, Point());
}
imshow("Output", img);
我尝试为数十种组合调整模糊和 Canny 参数,但仍然失败。我也试过用HoughLinesP找几组不同参数的slab边缘,也没有成功。
我是计算机视觉的新手,现在遇到的问题是:
- 我是否正在以错误的方式或策略寻找板坯轮廓?有没有更好的算法或组合?还是我需要专注于调整 Canny/findContours/HoughLinesP 算法的参数?
- 这种图片真的因为背景复杂很难处理吗?
我乐于接受任何可能帮助我完成目标的建议。提前谢谢你。
您可以考虑的技术
- 模板匹配,不同的可能需要准备很多模板 大理石(光线条件、旋转等)
- 训练分类器+区域建议,只有在其他解决方案失败时才采用此解决方案(此解决方案可能是最稳健的,但也是实施起来最冗长的)
由于您只有 10~20 种大理石板,我认为解决方案 1 是一个好的开始。
手动找出大理石的4个角点,做透视变换
pair<Mat, vector<Point2f>> get_target_marble(Mat const &input, vector<Point2f> const &src_pts) { using namespace cv; using namespace std; Point2f const tl = src_pts[0]; Point2f const tr = src_pts[1]; Point2f const br = src_pts[2]; Point2f const bl = src_pts[3]; auto const euclidean_dist = [](Point const &a, Point const &b) { return std::sqrt(std::pow(a.x-b.x, 2) + std::pow(a.y - b.y, 2)); }; int const max_width = static_cast<int>(std::max(euclidean_dist(br, bl), euclidean_dist(tr, tl))); int const max_height = static_cast<int>(std::max(euclidean_dist(tr, br), euclidean_dist(tl, bl))); vector<Point2f> const src{tl, tr, br, bl}; vector<Point2f> dst{Point(0,0), Point(max_width -1,0), Point(max_width-1,max_height-1), Point(0,max_height-1)}; Mat const hmat = getPerspectiveTransform(src, dst); Mat target; warpPerspective(input, target, hmat, {max_width, max_height}); return std::make_pair(std::move(target), std::move(dst));
}
找出查询图像(大理石板)和训练图像(图像可能包含大理石板)之间的单应矩阵
Mat find_homography(Mat const &train, Mat const &query) { Ptr<AKAZE> akaze = AKAZE::create(); vector<KeyPoint> query_kpts, train_kpts; cv::Mat query_desc, train_desc; akaze->detectAndCompute(train, cv::noArray(), query_kpts, query_desc); akaze->detectAndCompute(query, cv::noArray(), train_kpts, train_desc); BFMatcher matcher(NORM_HAMMING); vector<vector<DMatch>> nn_matches; //top 2 matches because we need to apply David Lowe's ratio test matcher.knnMatch(train_desc, query_desc, nn_matches, 2); vector<KeyPoint> matches1, matches2; for(auto const &m : nn_matches){ float const dist1 = m[0].distance; float const dist2 = m[1].distance; if(dist1 < 0.7 * dist2){ matches1.emplace_back(train_kpts[m[0].queryIdx]); matches2.emplace_back(query_kpts[m[0].trainIdx]); } } if(matches1.size() > 4){ std::vector<cv::Point2f> points1, points2; for(size_t i = 0; i != matches1.size(); ++i){ points1.emplace_back(matches1[i].pt); points2.emplace_back(matches2[i].pt); } return cv::findHomography(points1, points2, cv::RANSAC, 5.0); } return {};
}
将4行查询图像映射到目标图像
vector<Point2f> query_points; vector<Point> qpts; perspectiveTransform(dst, query_points, hmat); for(auto const &pt : query_points){ cout<<pt<<endl; qpts.emplace_back(pt); } polylines(input, qpts, true, {255,0,0}, 2);
你需要为这个解决方案准备10~20张图片,更喜欢那个 存在大多数匹配点以定位您的大理石板。如果 性能是一个问题,降低图像的分辨率,你不 需要一张大图才能得到结果。
完整代码放在github。
ps : 我不知道你的项目细节,如果只有10~20种大理石板+它们都有很好的特征可以追踪,你不需要3个月来解决它(但你可以告诉你的 bosses/customers 你需要 3 个月 :),有时更好的表现只会导致更多的家务活,而不是更多的钱)。