在 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. 计算石板的面积(相机到石板的距离和镜头的参数已知)

我使用的策略是: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边缘,也没有成功。

我是计算机视觉的新手,现在遇到的问题是:

  1. 我是否正在以错误的方式或策略寻找板坯轮廓?有没有更好的算法或组合?还是我需要专注于调整 Canny/findContours/HoughLinesP 算法的参数?
  2. 这种图片真的因为背景复杂很难处理吗?

我乐于接受任何可能帮助我完成目标的建议。提前谢谢你。

您可以考虑的技术

  1. 模板匹配,不同的可能需要准备很多模板 大理石(光线条件、旋转等)
  2. 训练分类器+区域建议,只有在其他解决方案失败时才采用此解决方案(此解决方案可能是最稳健的,但也是实施起来最冗长的)

由于您只有 10~20 种大理石板,我认为解决方案 1 是一个好的开始。

  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));
    

    }

  1. 找出查询图像(大理石板)和训练图像(图像可能包含大理石板)之间的单应矩阵

    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 {};
    

    }

  1. 将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 个月 :),有时更好的表现只会导致更多的家务活,而不是更多的钱)。