用opencv測量物體大概角度的一個比較笨的思路

hipeppa發表於2020-12-09

用opencv測量物體大概角度的一個比較笨的思路

檢測Faster-RCNN或者YOLO目標檢測演算法後的bbox中物體的大概角度。
1.首先將原圖分為BGR三個通道,將其中兩個通道加權求和之後進行灰度化,我這裡採用的是4G-1B,如果下一步驟的閾值分割效果不好,可以嘗試一下其他兩個通道加權求和。

Mat mv[3];
split(src,mv);
Mat img;
addWeighted(mv[1],4,mv[0],-1,0,img);

2.使用閾值分割,這裡推薦Otsu閾值分割和迭代閾值分割,這兩種的效果最好,程式碼如下

Mat gray;
gray=IterationThreshold(img);
//Otsu閾值分割
Mat OtsuAlgThreshold(Mat &image)
{
    if (image.channels() != 1)
    {
        cout << "Please input Gray-image!" << endl;
    }
    int T = 0; //Otsu演算法閾值
    double varValue = 0; //類間方差中間值儲存
    double w0 = 0; //前景畫素點數所佔比例
    double w1 = 0; //背景畫素點數所佔比例
    double u0 = 0; //前景平均灰度
    double u1 = 0; //背景平均灰度
    double Histogram[256] = { 0 }; //灰度直方圖,下標是灰度值,儲存內容是灰度值對應的畫素點總數
    uchar *data = image.data;

    double totalNum = image.rows*image.cols; //畫素總數

    for (int i = 0; i < image.rows; i++)
    {
        for (int j = 0; j < image.cols; j++)
        {
            if (image.at<uchar>(i, j) != 0) Histogram[data[i*image.step + j]]++;
        }
    }
    int minpos, maxpos;
    for (int i = 0; i < 255; i++)
    {
        if (Histogram[i] != 0)
        {
            minpos = i;
            break;
        }
    }
    for (int i = 255; i > 0; i--)
    {
        if (Histogram[i] != 0)
        {
            maxpos = i;
            break;
        }
    }

    for (int i = minpos; i <= maxpos; i++)
    {
        //每次遍歷之前初始化各變數
        w1 = 0;       u1 = 0;       w0 = 0;       u0 = 0;
        //***********背景各分量值計算**************************
        for (int j = 0; j <= i; j++) //背景部分各值計算
        {
            w1 += Histogram[j];   //背景部分畫素點總數
            u1 += j*Histogram[j]; //背景部分畫素總灰度和
        }
        if (w1 == 0) //背景部分畫素點數為0時退出
        {
            break;
        }
        u1 = u1 / w1; //背景畫素平均灰度
        w1 = w1 / totalNum; // 背景部分畫素點數所佔比例
        //***********背景各分量值計算**************************

        //***********前景各分量值計算**************************
        for (int k = i + 1; k < 255; k++)
        {
            w0 += Histogram[k];  //前景部分畫素點總數
            u0 += k*Histogram[k]; //前景部分畫素總灰度和
        }
        if (w0 == 0) //前景部分畫素點數為0時退出
        {
            break;
        }
        u0 = u0 / w0; //前景畫素平均灰度
        w0 = w0 / totalNum; // 前景部分畫素點數所佔比例
        //***********前景各分量值計算**************************

        //***********類間方差計算******************************
        double varValueI = w0*w1*(u1 - u0)*(u1 - u0); //當前類間方差計算
        if (varValue < varValueI)
        {
            varValue = varValueI;
            T = i;
        }
    }
    Mat dst;
    threshold(image, dst, T, 255, CV_THRESH_OTSU);
    return dst;
}
//迭代閾值分割
Mat IterationThreshold(Mat src)
{
    int width = src.cols;
    int height = src.rows;
    int hisData[256] = { 0 };
    for (int j = 0; j < height; j++)
    {
        uchar* data = src.ptr<uchar>(j);
        for (int i = 0; i < width; i++)
            hisData[data[i]]++;
    }

    int T0 = 0;
    for (int i = 0; i < 256; i++)
    {
        T0 += i*hisData[i];
    }
    T0 /= width*height;

    int T1 = 0, T2 = 0;
    int num1 = 0, num2 = 0;
    int T = 0;
    while (1)
    {
        for (int i = 0; i < T0 + 1; i++)
        {
            T1 += i*hisData[i];
            num1 += hisData[i];
        }
        if (num1 == 0)
            continue;
        for (int i = T0 + 1; i < 256; i++)
        {
            T2 += i*hisData[i];
            num2 += hisData[i];
        }
        if (num2 == 0)
            continue;

        T = (T1 / num1 + T2 / num2) / 2;

        if (T == T0)
            break;
        else
            T0 = T;
    }

    Mat dst;
    threshold(src, dst, T, 255, 0);
    return dst;
}

3.膨脹處理

Mat morphologyDst;
    cv::morphologyEx(gray, morphologyDst, cv::MORPH_DILATE,
                     cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 1)));

4.找出最小外接矩形,由於進行膨脹處理後,圖中還有一些黑點,沒有用孔洞填充,直接用一個最笨的方法,由於圖中只有一個物體,因此,可以遍歷其中所有的最小外接矩形,選出最大的一個,就是要找的那個,也嘗試一下孔洞填充,但是感覺效果不咋地,孔洞填充程式碼如下,需要的可以嘗試一下。

vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(morphologyDst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
    vector<RotatedRect> minRect(contours.size());
    float area=0;
    for( int i = 0; i < contours.size(); i++ )
    {
        minRect[i] = minAreaRect( Mat(contours[i]) );
        area=max(area,minRect[i].size.area());
    }
//孔洞填充
void fillHole(const Mat srcBw, Mat &dstBw)
{
    Size m_Size = srcBw.size();
    Mat Temp=Mat::zeros(m_Size.height+2,m_Size.width+2,srcBw.type());//延展影像
    srcBw.copyTo(Temp(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)));

    cv::floodFill(Temp, Point(0, 0), Scalar(255));

    Mat cutImg;//裁剪延展的影像
    Temp(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)).copyTo(cutImg);

    dstBw = srcBw | (~cutImg);
}

5.將外接矩形框畫出,這裡有個比較重要的是,opencv提供的RotatedRect中的角度是相對角度,(-90,0],需要將其轉換為絕對角度。

    Mat drawing=src;
    for( int i = 0; i< contours.size(); i++ )
    {
        if(minRect[i].size.area()==area){
            Point2f rect_points[4];
            minRect[i].points(rect_points);
            for( int j = 0; j < 4; j++ )line( drawing, rect_points[j], rect_points[(j+1)%4], Scalar(0,255,0), 2, 8 );
            double degree1=getRcDegree(minRect[i]);
            cout << "X:" << minRect[i].center.x << "Y:" << minRect[i].center.y <<endl<< "Angle:" << degree1 << endl;
        }
    }
//將相對角度轉化為絕對角度
static double calcLineDegree(const Point2f& firstPt, const Point2f& secondPt)
{
    double curLineAngle = 0.0f;
    if (secondPt.x - firstPt.x != 0)
    {
        curLineAngle = atan(static_cast<double>(firstPt.y - secondPt.y) / static_cast<double>(secondPt.x - firstPt.x));
        if (curLineAngle < 0)
        {
            curLineAngle += CV_PI;
        }
    }
    else
    {
        curLineAngle = CV_PI / 2.0f; //90度
    }
    return curLineAngle*180.0f/CV_PI;
}
static double getRcDegree(const RotatedRect box)
{
    double degree = 0.0f;
    Point2f vertVect[4];
    box.points(vertVect);
    //line 1
    const double firstLineLen = (vertVect[1].x - vertVect[0].x)*(vertVect[1].x - vertVect[0].x) +
                                (vertVect[1].y - vertVect[0].y)*(vertVect[1].y - vertVect[0].y);
    //line 2
    const double secondLineLen = (vertVect[2].x - vertVect[1].x)*(vertVect[2].x - vertVect[1].x) +
                                 (vertVect[2].y - vertVect[1].y)*(vertVect[2].y - vertVect[1].y);
    if (firstLineLen > secondLineLen)
    {
        degree = calcLineDegree(vertVect[0], vertVect[1]);
    }
    else
    {
        degree = calcLineDegree(vertVect[2], vertVect[1]);
    }
    return degree;
}

檢測效果
圖片1:
在這裡插入圖片描述

X:91.045Y:118.096
Angle:130.006

圖片2:
在這裡插入圖片描述

X:91.0245Y:126.922
Angle:72.5528

圖片3:
在這裡插入圖片描述

X:107.535Y:121.901
Angle:30.6997

相關文章