首页 文章

从轮廓OpenCV中检测卡MinArea Quadrilateral

提问于
浏览
2

另一个关于检测图片中的卡片 . 我已经成功地将图片中的卡片隔离开了,我有一个靠近的凸包,从这里我就卡住了 .

对于上下文/约束,目标:

  • 检测图片中的卡片

  • 平原背景(见例)

  • 前面固定的卡片类型(含义:我们有宽高比)

  • 每张图片一个对象(至少目前为止)

我使用的方法:

  • 缩小范围

  • 灰度

  • 光线模糊

  • Canny

  • 查找轮廓

  • 删除列表中少于120个点的所有轮廓(尝试/错误值)

  • 案例1:我有1个轮廓:我卡的完美轮廓:第9步

  • 案例2:我有多个轮廓

  • 凸壳

  • 近似多边形?

  • ???

步骤1,3和6主要是去除噪声和小的伪影 .

所以我几乎停留在第9步 . 我试过一个示例图片:

Sample

在调试图片上:

  • 绿色:轮廓

  • 红色:凸壳

  • Purple / Pink-ish:使用了aboutPolyDp

  • 黄色:minAreaRect

(结果图像从minAreaRect中提取)

所以轮廓是可以接受的,我可以通过调整canny或第一次模糊的参数来做得更好一些 . 但是现在这是可以接受的,现在的问题是,如何才能得到形成“minarea quadrilateral”的4个点 . 正如你所看到的,minAreaRect给出了一个不完美的矩形,而且aboutPolyDp丢失了太多的卡片 .

我有什么方法可以解决这个问题吗?当我使用aboutPolyDp(我使用了 arcLength*0.1 )时,我尝试使用epsilon值,但没有 .

这种方法的另一个问题是在canny期间丢失一个角(参见示例)它将不起作用(除非使用minAreaRect时) . 但这可能在之前(通过更好的预处理)或之后(因为我们知道宽度/高度比)得到解决 .

enter image description here

这里没有要求代码,只是想法如何处理,

谢谢!

Edit :Yves Daoust的解决方案:

  • 从与谓词匹配的凸包中获取8个点:(最大化x,x y,y,-x y,-x,-x-y,-y,x-y)

  • 从这个八边形,取4个最长的边,得到交叉点

结果:

result

Edit 2: 使用Hough变换(而不是8个极值点)可以为找到4个边的所有情况提供更好的结果 . 如果找到超过4行,可能我们有重复,所以使用一些数学来尝试过滤并保留4行 . 我使用行列式编写了一个草稿(如果平行则接近0)和点线距离公式

2 回答

  • 2

    这是我在输入图像上尝试的管道:

    步骤1:检测egdes

    • Blur 灰度输入并使用 Canny filter 检测边缘

    步骤2:找到卡片的角落

    • 计算 contours

    • Sort length 的轮廓并且只保留 largest 一个

    • 生成此轮廓的 convex hull

    • 从凸包中创建一个 mask

    • 使用 HoughLinesP 查找卡片的 4 sides

    • 计算4个边的 intersections

    第3步:Homography

    • 使用 findHomography 查找卡片的仿射变换(在步骤2中找到4个交叉点)

    • Warp 使用计算的单应矩阵输入图像

    这是结果:
    card detection pipeline

    请注意,您必须找到 sort the 4 intersection points 的方法,以便总是以相同的顺序(否则 findHomography 将无效) .

    我知道你没有要求代码,但我必须测试我的管道所以这里是...... :)

    Vec3f calcParams(Point2f p1, Point2f p2) // line's equation Params computation
    {
        float a, b, c;
        if (p2.y - p1.y == 0)
        {
            a = 0.0f;
            b = -1.0f;
        }
        else if (p2.x - p1.x == 0)
        {
            a = -1.0f;
            b = 0.0f;
        }
        else
        {
            a = (p2.y - p1.y) / (p2.x - p1.x);
            b = -1.0f;
        }
    
        c = (-a * p1.x) - b * p1.y;
        return(Vec3f(a, b, c));
    }
    
    Point findIntersection(Vec3f params1, Vec3f params2)
    {
        float x = -1, y = -1;
        float det = params1[0] * params2[1] - params2[0] * params1[1];
        if (det < 0.5f && det > -0.5f) // lines are approximately parallel
        {
            return(Point(-1, -1));
        }
        else
        {
            x = (params2[1] * -params1[2] - params1[1] * -params2[2]) / det;
            y = (params1[0] * -params2[2] - params2[0] * -params1[2]) / det;
        }
        return(Point(x, y));
    }
    
    vector<Point> getQuadrilateral(Mat & grayscale, Mat& output) // returns that 4 intersection points of the card
    {
        Mat convexHull_mask(grayscale.rows, grayscale.cols, CV_8UC1);
        convexHull_mask = Scalar(0);
    
        vector<vector<Point>> contours;
        findContours(grayscale, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    
        vector<int> indices(contours.size());
        iota(indices.begin(), indices.end(), 0);
    
        sort(indices.begin(), indices.end(), [&contours](int lhs, int rhs) {
            return contours[lhs].size() > contours[rhs].size();
        });
    
        /// Find the convex hull object
        vector<vector<Point> >hull(1);
        convexHull(Mat(contours[indices[0]]), hull[0], false);
    
        vector<Vec4i> lines;
        drawContours(convexHull_mask, hull, 0, Scalar(255));
        imshow("convexHull_mask", convexHull_mask);
        HoughLinesP(convexHull_mask, lines, 1, CV_PI / 200, 50, 50, 10);
        cout << "lines size:" << lines.size() << endl;
    
        if (lines.size() == 4) // we found the 4 sides
        {
            vector<Vec3f> params(4);
            for (int l = 0; l < 4; l++)
            {
                params.push_back(calcParams(Point(lines[l][0], lines[l][1]), Point(lines[l][2], lines[l][3])));
            }
    
            vector<Point> corners;
            for (int i = 0; i < params.size(); i++)
            {
                for (int j = i; j < params.size(); j++) // j starts at i so we don't have duplicated points
                {
                    Point intersec = findIntersection(params[i], params[j]);
                    if ((intersec.x > 0) && (intersec.y > 0) && (intersec.x < grayscale.cols) && (intersec.y < grayscale.rows))
                    {
                        cout << "corner: " << intersec << endl;
                        corners.push_back(intersec);
                    }
                }
            }
    
            for (int i = 0; i < corners.size(); i++)
            {
                circle(output, corners[i], 3, Scalar(0, 0, 255));
            }
    
            if (corners.size() == 4) // we have the 4 final corners
            {
                return(corners);
            }
        }
    
        return(vector<Point>());
    }
    
    int main(int argc, char** argv)
    {
        Mat input = imread("playingcard_input.png");
        Mat input_grey;
        cvtColor(input, input_grey, CV_BGR2GRAY);
        Mat threshold1;
        Mat edges;
        blur(input_grey, input_grey, Size(3, 3));
        Canny(input_grey, edges, 30, 100);
    
        vector<Point> card_corners = getQuadrilateral(edges, input);
        Mat warpedCard(400, 300, CV_8UC3);
        if (card_corners.size() == 4)
        {
            Mat homography = findHomography(card_corners, vector<Point>{Point(warpedCard.cols, 0), Point(warpedCard.cols, warpedCard.rows), Point(0,0) , Point(0, warpedCard.rows)});
            warpPerspective(input, warpedCard, homography, Size(warpedCard.cols, warpedCard.rows));
        }
    
        imshow("warped card", warpedCard);
        imshow("edges", edges);
        imshow("input", input);
        waitKey(0);
    
        return 0;
    }
    

    EDIT: 我已经调整了一些 CannyHoughLinesP 函数的参数,以便更好地检测卡片(程序现在适用于两个输入样本) .

  • 3

    由于物体在均匀的背景上被隔离,我建议开始从图像轮廓中找到边缘,朝向中心,并在第一个边缘点处停止 .

    除非你在背景区域得到假阳性,否则凸包将给你一个相当好的近似物体轮廓,尽管边缘点未命中 .

    现在要获得边界四边形,您可以找到八个基本方向上的最远点(最大化x,x y,y,x-y,-x,-x-y,-y,-x y) . 这会给你一个八边形(可能有合并的顶点) . 取四个最长的边并相交以找到角落 .

    enter image description here

相关问题