坚固的卡检测/持续校正OpenCV [英] Robust card detection/persecutive correction OpenCV

查看:347
本文介绍了坚固的卡检测/持续校正OpenCV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前有一种检测图像中的卡片的方法,大多数情况下,当照明相当一致,背景非常平静时,它会工作。



这里是我使用的代码预制此操作:

  Mat img = inImg.clone 
outImg = Mat(inImg.size(),CV_8UC1);
inImg.copyTo(outImg);

Mat img_fullRes = img.clone();

pyrDown(img,img);

Mat imgGray;
cvtColor(img,imgGray,CV_RGB2GRAY);
outImg_gray = imgGray.clone();
//查找边缘//

Mat detectedEdges = imgGray.clone();

bilateralFilter(imgGray,detectedEdges,0,185,3,0);
Canny(detectedEdges,detectedEdges,20,65,3);

dilate(detectedEdges,detectedEdges,Mat :: ones(3,3,CV_8UC1));
Mat cdst = img.clone();

vector< Vec4i>线;
HoughLinesP(detectedEdges,lines,1,CV_PI / 180,60,50,3);
for(size_t i = 0; i< lines.size(); i ++)
{
Vec4i l = lines [i];
// for debug
// line(cdst,cv :: Point(l [0],l [1]),cv :: Point(l [2],l [3] Scalar(0,0,255),1);
}
//cdst.copyTo(inImg);

// //找到交集点//

cv :: Rect imgROI;
int ext = 10;
imgROI.x = ext;
imgROI.y = ext;
imgROI.width = img.size()。width - ext;
imgROI.height = img.size()。height - ext;

int N = lines.size();
//创建N个点// N == lines.size()
cv :: Point ** poi = new cv :: Point * [N];
for(int i = 0; i poi [i] = new cv :: Point [N];
vector< cv :: Point> poiList;
for(int i = 0; i {
poi [i] [i] = cv :: Point(-1,-1)
Vec4i line1 = lines [i];
for(int j = i + 1; j {
Vec4i line2 = lines [j]
cv :: Point p = computeIntersect(line1,line2,imgROI);

if(px!= -1)
{
// line(cdst,p-cv :: Point(2,0),p + cv :: Point 2,0),Scalar(0,255,0));
// line(cdst,p-cv :: Point(0,2),p + cv :: Point(0,2),Scalar(0,255,0));

poiList.push_back(p);
}

poi [i] [j] = p;
poi [j] [i] = p;
}
}

cdst.copyTo(inImg);

if(poiList.size()== 0)
{
outImg = inImg.clone();
// circle(outImg,cv :: Point(100,100),50,Scalar(255,0,0),-1);
return;
}

convexHull(poiList,poiList,false,true);

for(int i = 0; i< poiList.size(); i ++)
{
cv :: Point p = poiList [i]
// circle(cdst,p,3,Scalar(255,0,0),2);
}
//评估所有可能的四边形

cv :: Point cardCorners [4];
float metric_max = 0;
int Npoi = poiList.size();
for(int p1 = 0; p1< Npoi; p1 ++)
{
cv :: Point pts [4]
pts [0] = poiList [p1];

for(int p2 = p1 + 1; p2 {
pts [1] = poiList [p2]
if(isCloseBy(pts [1],pts [0]))
continue;

for(int p3 = p2 + 1; p3 {
pts [2] = poiList [p3]
if(isCloseBy(pts [2],pts [1])|| isCloseBy(pts [2],pts [0]))
continue;


for(int p4 = p3 + 1; p4< Npoi; p4 ++)
{
pts [3] = poiList [p4]
if(isCloseBy(pts [3],pts [0])|| isCloseBy(pts [3],pts [1])$ ​​b $ b || isCloseBy(pts [3],pts [2] )
continue;


//获取指标
float area = getArea(pts);

cv :: Point a = pts [0] -pts [1];
cv :: Point b = pts [1] -pts [2];
cv :: Point c = pts [2] -pts [3];
cv :: Point d = pts [3] -pts [0];
float oppLenDiff = abs(a.dot(a)-c.dot(c))+ abs(b.dot(b)-d.dot(d));

float metric = area - 0.35 * oppLenDiff;
if(metric> metric_max)
{
metric_max = metric;
cardCorners [0] = pts [0];
cardCorners [1] = pts [1];
cardCorners [2] = pts [2];
cardCorners [3] = pts [3];
}

}
}
}
}

//找到对应于物理角card
sortPointsClockwise(cardCorners);

//计算同位素//

矢量< Point2f> srcPts(4);
srcPts [0] = cardCorners [0] * 2;
srcPts [1] = cardCorners [1] * 2;
srcPts [2] = cardCorners [2] * 2;
srcPts [3] = cardCorners [3] * 2;


vector< Point2f> dstPts(4);
cv :: Size outImgSize(1400,800);

dstPts [0] = Point2f(0,0);
dstPts [1] = Point2f(outImgSize.width-1,0);
dstPts [2] = Point2f(outImgSize.width-1,outImgSize.height-1);
dstPts [3] = Point2f(0,outImgSize.height-1);

Mat Homography = findHomography(srcPts,dstPts);

//应用同位体
warpPerspective(img_fullRes,outImg,Homography,outImgSize,INTER_CUBIC);
outImg.copyTo(inImg);

其中 computeIntersect 定义为:

  cv :: Point computeIntersect(cv :: Vec4i a,cv :: Vec4i b,cv :: Rect ROI)
{
int x1 = a [0],y1 = a [1],x2 = a [2],y2 = a [3]
int x3 = b [0],y3 = b [1],x4 = b [2],y4 = b [3]

cv :: Point p1 = cv :: Point(x1,y1);
cv :: Point p2 = cv :: Point(x2,y2);
cv :: Point p3 = cv :: Point(x3,y3);
cv :: Point p4 = cv :: Point(x4,y4);
//检查以确保所有点都在图像边界内,如果不拒绝它们。
if(!ROI.contains(p1)||!ROI.contains(p2)
||!ROI.contains(p3)||!ROI.contains(p4))
return cv :: Point(-1,-1);

cv :: Point vec1 = p1-p2;
cv :: Point vec2 = p3-p4;

float vec1_norm2 = vec1.x * vec1.x + vec1.y * vec1.y;
float vec2_norm2 = vec2.x * vec2.x + vec2.y * vec2.y;
float cosTheta =(vec1.dot(vec2))/ sqrt(vec1_norm2 * vec2_norm2);

float den =((float)(x1-x2)*(y3-y4)) - ((y1-y2)*(x3-x4));
if(den!= 0)
{
cv :: Point2f pt;
pt.x =((x1 * y2-y1 * x2)*(x3-x4) - (x1-x2)*(x3 * y4- y3 * x4)
pt.y =((x1 * y2-y1 * x2)*(y3-y4) - (y1-y2)*(x3 * y4-y3 * x4)

if(!ROI.contains(pt))
return cv :: Point(-1,-1);

//不可信度量
float d1 = MIN(dist2(p1,pt),dist2(p2,pt))/ vec1_norm2;
float d2 = MIN(dist2(p3,pt),dist2(p4,pt))/ vec2_norm2;

float no_confidence_metric = MAX(sqrt(d1),sqrt(d2));

//如果终点比率大于.5 reject
if(no_confidence_metric <0.5&& cosTheta <0.707)
return cv :: Point (pt.x + 0.5),int(pt.y + 0.5));
}

return cv :: Point(-1,-1);
}

sortPointsClockWise as:

  void sortPointsClockwise(cv :: Point a [])
{
cv :: Point b [4];

cv :: Point ctr =(a [0] + a [1] + a [2] + a [3]);
ctr.x / = 4;
ctr.y / = 4;
b [0] = a [0] -ctr;
b [1] = a [1] -ctr;
b [2] = a [2] -ctr;
b [3] = a [3] -ctr;

for(int i = 0; i <4; i ++)
{
if(b [i] .x <0)
{
if(b [i] .y <0)
a [0] = b [i] + ctr;
else
a [3] = b [i] + ctr;
}
else
{
if(b [i] .y <0)
a [1] = b [i] + ctr;
else
a [2] = b [i] + ctr;
}
}

}

getArea 定义为:

  float getArea(cv :: Point arr [] )
{
cv :: Point diag1 = arr [0] -arr [2];
cv :: Point diag2 = arr [1] -arr [3];

return 0.5 *(diag1.cross(diag2));
}

isCloseBy as:

  bool isCloseBy(cv :: Point p1,cv :: Point p2)
{
int D = 10;
//检查X值是否在10之内,Y值是否相同。
return(abs(p1.x-p2.x)< = D&& abs(p1.y-p2.y)< = D);
}

最后 dist2

  float dist2(cv :: Point p1,cv :: Point p2)
{
return float((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

以下是几个测试图片及其结果:












对不起,很长的帖子,但我希望有人可以建议一种方法,我可以使我的方法从图像中提取卡更强大。



当一张卡片放置在具有良好照明的对比背景上时,我的方法几乎可以工作90%的时间。但是很明显,我需要一个更健壮的方法。



有没有人有任何建议?



/ p>

dhanushka soloution的ATTEMPT

 灰色(毫克,inImg); 
cvtColor(inImg,grey,CV_RGB2GRAY);
int morph_size = 3;
Mat element = getStructuringElement(MORPH_ELLIPSE,cv :: Size(4 * morph_size + 1,2 * morph_size + 1),cv :: Point(morph_size,morph_size));

morphologyEx(gray,gray,2,element);
threshold(gray,bw,160,255,CV_THRESH_BINARY);

vector< vector< cv :: Point> >轮廓;
vector< Vec4i>层次;
findContours(bw,contour,hierarchy,CV_RETR_TREE,CV_CHAIN_APPROX_SIMPLE,cv :: Point(0,0));

int largest_area = 0;
int largest_contour_index = 0;
cv :: Rect bounding_rect;

for(int i = 0; i {
double a = contourArea(contoururs [i],false); //找到轮廓的区域
if(a> largest_area){
highest_area = a;
largest_contour_index = i; //存储最大轮廓的索引
bounding_rect = boundingRect(contoururs [i]);
}
}

//标量颜色(255,255,255);
rectangle(inImg,bounding_rect,Scalar(0,255,0),1,8,0);
Mat biggestRect = inImg(bounding_rect);
Mat card1 = largestRect.clone();


解决方案

更通用的方法肯定会像Rutger Nijlunsing建议在他的答案。然而,在你的情况下,至少对于提供的样本图像,一个非常简单的方法,如形态开口,然后阈值,轮廓处理和凸包将产生你想要的结果。使用缩小版本的图像进行处理,以便您不必使用大型内核进行形态操作。以下是以此方式处理的图片。

  pyrDown(large,rgb0); 
pyrDown(rgb0,rgb0);
pyrDown(rgb0,rgb0);

Mat small;
cvtColor(rgb0,small,CV_BGR2GRAY);

Mat morph;
Mat kernel = getStructuringElement(MORPH_ELLIPSE,Size(11,11));
morphologyEx(small,morph,MORPH_OPEN,kernel);

Mat bw;
threshold(morph,bw,0,255.0,CV_THRESH_BINARY | CV_THRESH_OTSU);

Mat bdry;
kernel = getStructuringElement(MORPH_ELLIPSE,Size(3,3));
erode(bw,bdry,kernel);
subtract(bw,bdry,bdry);

//对bdry执行轮廓处理








I currently have a method for detecting a card in an image and for the most part it works when the lighting is fairly consistent and the background is very calm.

Here is the code I am using to preform this operation:

    Mat img = inImg.clone();
    outImg = Mat(inImg.size(), CV_8UC1);
    inImg.copyTo(outImg);

    Mat img_fullRes = img.clone();

    pyrDown(img, img);

    Mat imgGray;
    cvtColor(img, imgGray, CV_RGB2GRAY);
    outImg_gray = imgGray.clone();
    // Find Edges //

    Mat detectedEdges = imgGray.clone();

    bilateralFilter(imgGray, detectedEdges, 0, 185, 3, 0);
    Canny( detectedEdges, detectedEdges, 20, 65, 3 );

    dilate(detectedEdges, detectedEdges, Mat::ones(3,3,CV_8UC1));
    Mat cdst = img.clone();

    vector<Vec4i> lines;
    HoughLinesP(detectedEdges, lines, 1, CV_PI/180, 60, 50, 3 );
    for( size_t i = 0; i < lines.size(); i++ )
    {
        Vec4i l = lines[i];
        // For debug
        //line( cdst, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), Scalar(0,0,255), 1);
    }
    //cdst.copyTo(inImg);

//    // Find points of intersection //

    cv::Rect imgROI;
    int ext = 10;
    imgROI.x = ext;
    imgROI.y = ext;
    imgROI.width = img.size().width - ext;
    imgROI.height = img.size().height - ext;

    int N = lines.size();
    // Creating N amount of points // N == lines.size()
    cv::Point** poi = new cv::Point*[N];
    for( int i = 0; i < N; i++ )
        poi[i] = new cv::Point[N];
    vector<cv::Point> poiList;
    for( int i = 0; i < N; i++ )
    {
        poi[i][i] = cv::Point(-1,-1);
        Vec4i line1 = lines[i];
        for( int j = i + 1; j < N; j++ )
        {
            Vec4i line2 = lines[j];
            cv::Point p = computeIntersect(line1, line2, imgROI);

            if( p.x != -1 )
            {
                //line(cdst, p-cv::Point(2,0), p+cv::Point(2,0), Scalar(0,255,0));
                //line(cdst, p-cv::Point(0,2), p+cv::Point(0,2), Scalar(0,255,0));

                poiList.push_back(p);
            }

            poi[i][j] = p;
            poi[j][i] = p;
        }
    }

    cdst.copyTo(inImg);

    if(poiList.size()==0)
    {
        outImg = inImg.clone();
        //circle(outImg, cv::Point(100,100), 50, Scalar(255,0,0), -1);
        return;
    }

    convexHull(poiList, poiList, false, true);

    for( int i=0; i<poiList.size(); i++ )
    {
        cv::Point p = poiList[i];
        //circle(cdst, p, 3, Scalar(255,0,0), 2);
    }
     //Evaluate all possible quadrilaterals

    cv::Point cardCorners[4];
    float metric_max = 0;
    int Npoi = poiList.size();
    for( int p1=0; p1<Npoi; p1++ )
    {
        cv::Point pts[4];
        pts[0] = poiList[p1];

        for( int p2=p1+1; p2<Npoi; p2++ )
        {
            pts[1] = poiList[p2];
            if( isCloseBy(pts[1],pts[0]) )
                continue;

            for( int p3=p2+1; p3<Npoi; p3++ )
            {
                pts[2] = poiList[p3];
                if( isCloseBy(pts[2],pts[1]) || isCloseBy(pts[2],pts[0]) )
                    continue;


                for( int p4=p3+1; p4<Npoi; p4++ )
                {
                    pts[3] = poiList[p4];
                    if( isCloseBy(pts[3],pts[0]) || isCloseBy(pts[3],pts[1])
                       || isCloseBy(pts[3],pts[2]) )
                        continue;


                    // get the metrics
                    float area = getArea(pts);

                    cv::Point a = pts[0]-pts[1];
                    cv::Point b = pts[1]-pts[2];
                    cv::Point c = pts[2]-pts[3];
                    cv::Point d = pts[3]-pts[0];
                    float oppLenDiff = abs(a.dot(a)-c.dot(c)) + abs(b.dot(b)-d.dot(d));

                    float metric = area - 0.35*oppLenDiff;
                    if( metric > metric_max )
                    {
                        metric_max = metric;
                        cardCorners[0] = pts[0];
                        cardCorners[1] = pts[1];
                        cardCorners[2] = pts[2];
                        cardCorners[3] = pts[3];
                    }

                }
            }
        }
    }

    // find the corners corresponding to the 4 corners of the physical card
    sortPointsClockwise(cardCorners);

    // Calculate Homography //

    vector<Point2f> srcPts(4);
    srcPts[0] = cardCorners[0]*2;
    srcPts[1] = cardCorners[1]*2;
    srcPts[2] = cardCorners[2]*2;
    srcPts[3] = cardCorners[3]*2;


    vector<Point2f> dstPts(4);
    cv::Size outImgSize(1400,800);

    dstPts[0] = Point2f(0,0);
    dstPts[1] = Point2f(outImgSize.width-1,0);
    dstPts[2] = Point2f(outImgSize.width-1,outImgSize.height-1);
    dstPts[3] = Point2f(0,outImgSize.height-1);

    Mat Homography = findHomography(srcPts, dstPts);

    // Apply Homography
    warpPerspective( img_fullRes, outImg, Homography, outImgSize, INTER_CUBIC );
    outImg.copyTo(inImg);

Where computeIntersect is defined as:

cv::Point computeIntersect(cv::Vec4i a, cv::Vec4i b, cv::Rect ROI)
{
    int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3];
    int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];

    cv::Point  p1 = cv::Point (x1,y1);
    cv::Point  p2 = cv::Point (x2,y2);
    cv::Point  p3 = cv::Point (x3,y3);
    cv::Point  p4 = cv::Point (x4,y4);
    // Check to make sure all points are within the image boundrys, if not reject them.
    if( !ROI.contains(p1) || !ROI.contains(p2)
       || !ROI.contains(p3) || !ROI.contains(p4) )
        return cv::Point (-1,-1);

    cv::Point  vec1 = p1-p2;
    cv::Point  vec2 = p3-p4;

    float vec1_norm2 = vec1.x*vec1.x + vec1.y*vec1.y;
    float vec2_norm2 = vec2.x*vec2.x + vec2.y*vec2.y;
    float cosTheta = (vec1.dot(vec2))/sqrt(vec1_norm2*vec2_norm2);

    float den = ((float)(x1-x2) * (y3-y4)) - ((y1-y2) * (x3-x4));
    if(den != 0)
    {
        cv::Point2f pt;
        pt.x = ((x1*y2 - y1*x2) * (x3-x4) - (x1-x2) * (x3*y4 - y3*x4)) / den;
        pt.y = ((x1*y2 - y1*x2) * (y3-y4) - (y1-y2) * (x3*y4 - y3*x4)) / den;

        if( !ROI.contains(pt) )
            return cv::Point (-1,-1);

        // no-confidence metric
        float d1 = MIN( dist2(p1,pt), dist2(p2,pt) )/vec1_norm2;
        float d2 = MIN( dist2(p3,pt), dist2(p4,pt) )/vec2_norm2;

        float no_confidence_metric = MAX(sqrt(d1),sqrt(d2));

        // If end point ratios are greater than .5 reject
        if( no_confidence_metric < 0.5 && cosTheta < 0.707 )
            return cv::Point (int(pt.x+0.5), int(pt.y+0.5));
    }

    return cv::Point(-1, -1);
}

sortPointsClockWise is defined as:

void sortPointsClockwise(cv::Point a[])
{
    cv::Point b[4];

    cv::Point ctr = (a[0]+a[1]+a[2]+a[3]);
    ctr.x /= 4;
    ctr.y /= 4;
    b[0] = a[0]-ctr;
    b[1] = a[1]-ctr;
    b[2] = a[2]-ctr;
    b[3] = a[3]-ctr;

    for( int i=0; i<4; i++ )
    {
        if( b[i].x < 0 )
        {
            if( b[i].y < 0 )
                a[0] = b[i]+ctr;
            else
                a[3] = b[i]+ctr;
        }
        else
        {
            if( b[i].y < 0 )
                a[1] = b[i]+ctr;
            else
                a[2] = b[i]+ctr;
        }
    }

}

getArea is defined as:

float getArea(cv::Point arr[])
{
    cv::Point  diag1 = arr[0]-arr[2];
    cv::Point  diag2 = arr[1]-arr[3];

    return 0.5*(diag1.cross(diag2));
}

isCloseBy is defined as:

bool isCloseBy( cv::Point p1, cv::Point p2 )
{
    int D = 10;
    // Checking that X values are within 10, same for Y values.
    return ( abs(p1.x-p2.x)<=D && abs(p1.y-p2.y)<=D );
}

And finally dist2:

float dist2( cv::Point p1, cv::Point p2 )
{
    return float((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
}

Here are several test images and their results:

Sorry for the very lengthy post, however I am hoping someone can suggest a way I can make my method for extracting the card from the image more robust. One that can better handle disruptive backgrounds along with inconsistent lighting.

When a card is placed on a contrasting background with good lighting my method works nearly 90% of the time. But it is clear I need a more robust approach.

Does anyone have any suggestions?

Thanks.

ATTEMPT of dhanushka's soloution

Mat gray, bw;     pyrDown(inImg, inImg);
cvtColor(inImg, gray, CV_RGB2GRAY);
int morph_size = 3;
Mat element = getStructuringElement( MORPH_ELLIPSE, cv::Size( 4*morph_size + 1, 2*morph_size+1 ), cv::Point( morph_size, morph_size ) );

morphologyEx(gray, gray, 2, element);
threshold(gray, bw, 160, 255, CV_THRESH_BINARY);

vector<vector<cv::Point> > contours;
vector<Vec4i> hierarchy;
findContours( bw, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

int largest_area=0;
int largest_contour_index=0;
cv::Rect bounding_rect;

for( int i = 0; i< contours.size(); i++ )
{
    double a=contourArea( contours[i],false);  //  Find the area of contour
    if(a>largest_area){
        largest_area=a;
        largest_contour_index=i;                //Store the index of largest contour
        bounding_rect=boundingRect(contours[i]);
    }
}

//Scalar color( 255,255,255);
rectangle(inImg, bounding_rect,  Scalar(0,255,0),1, 8,0);
Mat biggestRect = inImg(bounding_rect);
Mat card1 = biggestRect.clone();

解决方案

A more general approach would definitely be something like Rutger Nijlunsing suggested in his answer. However, in your case, at least for the provided sample images, a very simple approach like morphological opening followed by thresholding, contour processing and convexhull would yield the result you want. Use a scaled down version of the images for processing so that you don't have to use a large kernel for morphological operations. Below are the images processed this way.

    pyrDown(large, rgb0);
    pyrDown(rgb0, rgb0);
    pyrDown(rgb0, rgb0);

    Mat small;
    cvtColor(rgb0, small, CV_BGR2GRAY);

    Mat morph;
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(11, 11));
    morphologyEx(small, morph, MORPH_OPEN, kernel);

    Mat bw;
    threshold(morph, bw, 0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU);

    Mat bdry;
    kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    erode(bw, bdry, kernel);
    subtract(bw, bdry, bdry);

    // do contour processing on bdry

This approach will not work in general, so I would strongly recommend something like Rutger suggested.

这篇关于坚固的卡检测/持续校正OpenCV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆