用opencv測量物體大概角度的一個比較笨的思路
阿新 • • 發佈:2020-12-13
用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