RM裝甲識別程序分析
目錄
1. 代碼預覽
1.1 基於opencv2.4.9編寫
//來自網絡並非原創
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include "cxcore.h"
#include "omp.h"
#include "iostream"
using namespace cv;
using namespace std;
#define T_ANGLE_THRE 10
#define T_SIZE_THRE 5
void BrightAdjust(IplImage* src, IplImage* dst, //亮度調節函數
double dContrast, double dBright)
{
int nVal;
unsigned char* SrcData = (unsigned char*)src->imageData; //原圖像數據區指針
unsigned char* DstData = (unsigned char*)dst->imageData; //目的圖像數據區指針
int step = src->widthStep / sizeof(unsigned char) / 3; //一行有多少個像素
omp_set_num_threads(8);
#pragma omp parallel for
for (int nI = 0; nI<src->height; nI++)
{
for (int nJ = 0; nJ <src->width; nJ++)
{
for (int nK = 0; nK < 3; nK++)
{
nVal = (int)(dContrast * SrcData[(nI*step + nJ) * 3 + nK] + dBright); //每個像素的每個通道的值都進行線性變換
if (nVal < 0)
nVal = 0;
if (nVal > 255)
nVal = 255;
DstData[(nI*step + nJ) * 3 + nK] = nVal;
}
}
}
}
void GetDiffImage(IplImage* src1, IplImage* src2, IplImage* dst, int nThre)
{
unsigned char* SrcData1 = (unsigned char*)src1->imageData;
unsigned char* SrcData2 = (unsigned char*)src2->imageData;
unsigned char* DstData = (unsigned char*)dst->imageData;
int step = src1->widthStep / sizeof(unsigned char);
omp_set_num_threads(8);
#pragma omp parallel for
for (int nI = 0; nI<src1->height; nI++)
{
for (int nJ = 0; nJ <src1->width; nJ++)
{
if (SrcData1[nI*step + nJ] - SrcData2[nI*step + nJ]> nThre) //
{
DstData[nI*step + nJ] = 255;
}
else
{
DstData[nI*step + nJ] = 0;
}
}
}
}
vector<CvBox2D> ArmorDetect(vector<CvBox2D> vEllipse)
{
vector<CvBox2D> vRlt;
CvBox2D Armor; //定義裝甲區域的旋轉矩形
int nL, nW;
double dAngle;
vRlt.clear();
if (vEllipse.size() < 2) //如果檢測到的旋轉矩形個數小於2,則直接返回
return vRlt;
for (unsigned int nI = 0; nI < vEllipse.size() - 1; nI++) //求任意兩個旋轉矩形的夾角
{
for (unsigned int nJ = nI + 1; nJ < vEllipse.size(); nJ++)
{
dAngle = abs(vEllipse[nI].angle - vEllipse[nJ].angle);
while (dAngle > 180)
dAngle -= 180;
if ((dAngle < T_ANGLE_THRE || 180 - dAngle < T_ANGLE_THRE) && abs(vEllipse[nI].size.height - vEllipse[nJ].size.height) < (vEllipse[nI].size.height + vEllipse[nJ].size.height) / T_SIZE_THRE && abs(vEllipse[nI].size.width - vEllipse[nJ].size.width) < (vEllipse[nI].size.width + vEllipse[nJ].size.width) / T_SIZE_THRE) //判斷這兩個旋轉矩形是否是一個裝甲的兩個LED等條
{
Armor.center.x = (vEllipse[nI].center.x + vEllipse[nJ].center.x) / 2; //裝甲中心的x座標
Armor.center.y = (vEllipse[nI].center.y + vEllipse[nJ].center.y) / 2; //裝甲中心的y座標
Armor.angle = (vEllipse[nI].angle + vEllipse[nJ].angle) / 2; //裝甲所在旋轉矩形的旋轉角度
if (180 - dAngle < T_ANGLE_THRE)
Armor.angle += 90;
nL = (vEllipse[nI].size.height + vEllipse[nJ].size.height) / 2; //裝甲的高度
nW = sqrt((vEllipse[nI].center.x - vEllipse[nJ].center.x) * (vEllipse[nI].center.x - vEllipse[nJ].center.x) + (vEllipse[nI].center.y - vEllipse[nJ].center.y) * (vEllipse[nI].center.y - vEllipse[nJ].center.y)); //裝甲的寬度等於兩側LED所在旋轉矩形中心座標的距離
if (nL < nW)
{
Armor.size.height = nL;
Armor.size.width = nW;
}
else
{
Armor.size.height = nW;
Armor.size.width = nL;
}
vRlt.push_back(Armor); //將找出的裝甲的旋轉矩形保存到vector
}
}
}
return vRlt;
}
void DrawBox(CvBox2D box, IplImage* img)
{
CvPoint2D32f point[4];
int i;
for (i = 0; i<4; i++)
{
point[i].x = 0;
point[i].y = 0;
}
cvBoxPoints(box, point); //計算二維盒子頂點
CvPoint pt[4];
for (i = 0; i<4; i++)
{
pt[i].x = (int)point[i].x;
pt[i].y = (int)point[i].y;
}
cvLine(img, pt[0], pt[1], CV_RGB(0, 0, 255), 2, 8, 0);
cvLine(img, pt[1], pt[2], CV_RGB(0, 0, 255), 2, 8, 0);
cvLine(img, pt[2], pt[3], CV_RGB(0, 0, 255), 2, 8, 0);
cvLine(img, pt[3], pt[0], CV_RGB(0, 0, 255), 2, 8, 0);
}
int main()
{
CvCapture* pCapture0 = cvCreateFileCapture("RawImage\\RedCar.avi");
//CvCapture* pCapture0 = cvCreateCameraCapture(0);
IplImage* pFrame0 = NULL;
CvSize pImgSize;
CvBox2D s; //定義旋轉矩形
vector<CvBox2D> vEllipse; //定以旋轉矩形的向量,用於存儲發現的目標區域
vector<CvBox2D> vRlt;
vector<CvBox2D> vArmor;
CvScalar sl;
bool bFlag = false;
CvSeq *pContour = NULL;
CvMemStorage *pStorage = cvCreateMemStorage(0);
pFrame0 = cvQueryFrame(pCapture0);
pImgSize = cvGetSize(pFrame0);
IplImage *pRawImg = cvCreateImage(pImgSize, IPL_DEPTH_8U, 3);
IplImage* pGrayImage = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
IplImage* pRImage = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
IplImage* pGImage = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
IplImage *pBImage = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
IplImage *pBinary = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
IplImage *pRlt = cvCreateImage(pImgSize, IPL_DEPTH_8U, 1);
CvSeq* lines = NULL;
CvMemStorage* storage = cvCreateMemStorage(0);
while (1)
{
if (pFrame0)
{
BrightAdjust(pFrame0, pRawImg, 1, -120); //每個像素每個通道的值都減去120
cvSplit(pRawImg, pBImage, pGImage, pRImage, 0); //將三個通道的像素值分離
//如果像素R值-G值大於25,則返回的二值圖像的值爲255,否則爲0
GetDiffImage(pRImage, pGImage, pBinary, 25);
cvDilate(pBinary, pGrayImage, NULL, 3); //圖像膨脹
cvErode(pGrayImage, pRlt, NULL, 1); //圖像腐蝕,先膨脹在腐蝕屬於閉運算
cvFindContours(pRlt, pStorage, &pContour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE); //在二值圖像中尋找輪廓
for (; pContour != NULL; pContour = pContour->h_next)
{
if (pContour->total > 10) //判斷當前輪廓是否大於10個像素點
{
bFlag = true; //如果大於10個,則檢測到目標區域
//擬合目標區域成爲橢圓,返回一個旋轉矩形(中心、角度、尺寸)
s = cvFitEllipse2(pContour);
for (int nI = 0; nI < 5; nI++)
{
for (int nJ = 0; nJ < 5; nJ++) //遍歷以旋轉矩形中心點爲中心的5*5的像素塊
{
if (s.center.y - 2 + nJ > 0 && s.center.y - 2 + nJ < 480 && s.center.x - 2 + nI > 0 && s.center.x - 2 + nI < 640) //判斷該像素是否在有效的位置
{
sl = cvGet2D(pFrame0, (int)(s.center.y - 2 + nJ), (int)(s.center.x - 2 + nI)); //獲取遍歷點點像素值
//判斷中心點是否接近白色
if (sl.val[0] < 200 || sl.val[1] < 200 || sl.val[2] < 200)
bFlag = false; //如果中心不是白色,則不是目標區域
}
}
}
if (bFlag)
{
vEllipse.push_back(s); //將發現的目標保存
//cvEllipseBox(pFrame0, s, CV_RGB(255, 0, 0), 2, 8, 0);
}
}
}
//調用子程序,在輸入的LED所在旋轉矩形的vector中找出裝甲的位置,幷包裝成旋轉矩形,存入vector並返回
vRlt = ArmorDetect(vEllipse);
for (unsigned int nI = 0; nI < vRlt.size(); nI++) //在當前圖像中標出裝甲的位置
DrawBox(vRlt[nI], pFrame0);
cvShowImage("Raw", pFrame0);
if (cvWaitKey(50) == 27)
{
break;
}
vEllipse.clear();
vRlt.clear();
vArmor.clear();
pFrame0 = cvQueryFrame(pCapture0);
}
else
{
break;
}
}
cvReleaseCapture(&pCapture0);
return 0;
}
1.2 基於opencv3.0.0編寫
//根據以上代碼將其在opencv3.0.0中編寫
#include "stdafx.h"
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/imgproc.hpp"
#include "iostream"
#include "omp.h"
using namespace cv;
using namespace std;
#define T_ANGLE_THRE 10
#define T_SIZE_THRE 5
void brightAdjust(Mat src, Mat dst, double dContrast, double dBright); //亮度調節函數
void getDiffImage(Mat src1, Mat src2, Mat dst, int nThre); //二值化
vector<RotatedRect> armorDetect(vector<RotatedRect> vEllipse); //檢測裝甲
void drawBox(RotatedRect box, Mat img); //標記裝甲
int main()
{
VideoCapture cap0("RawImage\\RedCar.avi");
Mat frame0;
Size imgSize;
RotatedRect s; //定義旋轉矩形
vector<RotatedRect> vEllipse; //定以旋轉矩形的向量,用於存儲發現的目標區域
vector<RotatedRect> vRlt;
vector<RotatedRect> vArmor;
bool bFlag = false;
vector<vector<Point> > contour;
cap0 >> frame0;
imgSize = frame0.size();
Mat rawImg = Mat(imgSize, CV_8UC3);
Mat grayImage = Mat(imgSize, CV_8UC1);
Mat rImage = Mat(imgSize, CV_8UC1);
Mat gImage = Mat(imgSize, CV_8UC1);
Mat bImage = Mat(imgSize, CV_8UC1);
Mat binary = Mat(imgSize, CV_8UC1);
Mat rlt = Mat(imgSize, CV_8UC1);
namedWindow("Raw");
while (1)
{
if (cap0.read(frame0))
{
brightAdjust(frame0, rawImg, 1, -120); //每個像素每個通道的值都減去120
Mat bgr[3];
split(rawImg, bgr); //將三個通道的像素值分離
bImage = bgr[0];
gImage = bgr[1];
rImage = bgr[2];
//如果像素R值-G值大於25,則返回的二值圖像的值爲255,否則爲0
getDiffImage(rImage, gImage, binary, 25);
dilate(binary, grayImage, Mat(), Point(-1,-1), 3); //圖像膨脹
erode(grayImage, rlt, Mat(), Point(-1,-1), 1); //圖像腐蝕,先膨脹在腐蝕屬於閉運算
findContours(rlt, contour, RETR_CCOMP , CHAIN_APPROX_SIMPLE); //在二值圖像中尋找輪廓
for (int i=0; i<contour.size(); i++)
{
if (contour[i].size()> 10) //判斷當前輪廓是否大於10個像素點
{
bFlag = true; //如果大於10個,則檢測到目標區域
//擬合目標區域成爲橢圓,返回一個旋轉矩形(中心、角度、尺寸)
s = fitEllipse(contour[i]);
for (int nI = 0; nI < 5; nI++)
{
for (int nJ = 0; nJ < 5; nJ++) //遍歷以旋轉矩形中心點爲中心的5*5的像素塊
{
if (s.center.y - 2 + nJ > 0 && s.center.y - 2 + nJ < 480 && s.center.x - 2 + nI > 0 && s.center.x - 2 + nI < 640) //判斷該像素是否在有效的位置
{
Vec3b v3b = frame0.at<Vec3b>((int)(s.center.y - 2 + nJ), (int)(s.center.x - 2 + nI)); //獲取遍歷點點像素值
//判斷中心點是否接近白色
if (v3b[0] < 200 || v3b[1] < 200 || v3b[2] < 200)
bFlag = false; //如果中心不是白色,則不是目標區域
}
}
}
if (bFlag)
{
vEllipse.push_back(s); //將發現的目標保存
}
}
}
//調用子程序,在輸入的LED所在旋轉矩形的vector中找出裝甲的位置,幷包裝成旋轉矩形,存入vector並返回
vRlt = armorDetect(vEllipse);
for (unsigned int nI = 0; nI < vRlt.size(); nI++) //在當前圖像中標出裝甲的位置
drawBox(vRlt[nI], frame0);
imshow("Raw", frame0);
if (waitKey(50) == 27)
{
break;
}
vEllipse.clear();
vRlt.clear();
vArmor.clear();
}
else
{
break;
}
}
cap0.release();
return 0;
}
void brightAdjust(Mat src, Mat dst, double dContrast, double dBright)
{
int nVal;
omp_set_num_threads(8);
#pragma omp parallel for
for (int nI = 0; nI<src.rows; nI++)
{
Vec3b* p1 = src.ptr<Vec3b>(nI);
Vec3b* p2 = dst.ptr<Vec3b>(nI);
for (int nJ = 0; nJ <src.cols; nJ++)
{
for (int nK = 0; nK < 3; nK++)
{
//每個像素的每個通道的值都進行線性變換
nVal = (int)(dContrast * p1[nJ][nK] + dBright);
if (nVal < 0)
nVal = 0;
if (nVal > 255)
nVal = 255;
p2[nJ][nK] = nVal;
}
}
}
}
void getDiffImage(Mat src1, Mat src2, Mat dst, int nThre)
{
omp_set_num_threads(8);
#pragma omp parallel for
for (int nI = 0; nI<src1.rows; nI++)
{
uchar* pchar1 = src1.ptr<uchar>(nI);
uchar* pchar2 = src2.ptr<uchar>(nI);
uchar* pchar3 = dst.ptr<uchar>(nI);
for (int nJ = 0; nJ <src1.cols; nJ++)
{
if (pchar1[nJ] - pchar2[nJ]> nThre) //
{
pchar3[nJ] = 255;
}
else
{
pchar3[nJ] = 0;
}
}
}
}
vector<RotatedRect> armorDetect(vector<RotatedRect> vEllipse)
{
vector<RotatedRect> vRlt;
RotatedRect armor; //定義裝甲區域的旋轉矩形
int nL, nW;
double dAngle;
vRlt.clear();
if (vEllipse.size() < 2) //如果檢測到的旋轉矩形個數小於2,則直接返回
return vRlt;
for (unsigned int nI = 0; nI < vEllipse.size() - 1; nI++) //求任意兩個旋轉矩形的夾角
{
for (unsigned int nJ = nI + 1; nJ < vEllipse.size(); nJ++)
{
dAngle = abs(vEllipse[nI].angle - vEllipse[nJ].angle);
while (dAngle > 180)
dAngle -= 180;
//判斷這兩個旋轉矩形是否是一個裝甲的兩個LED等條
if ((dAngle < T_ANGLE_THRE || 180 - dAngle < T_ANGLE_THRE) && abs(vEllipse[nI].size.height - vEllipse[nJ].size.height) < (vEllipse[nI].size.height + vEllipse[nJ].size.height) / T_SIZE_THRE && abs(vEllipse[nI].size.width - vEllipse[nJ].size.width) < (vEllipse[nI].size.width + vEllipse[nJ].size.width) / T_SIZE_THRE)
{
armor.center.x = (vEllipse[nI].center.x + vEllipse[nJ].center.x) / 2; //裝甲中心的x座標
armor.center.y = (vEllipse[nI].center.y + vEllipse[nJ].center.y) / 2; //裝甲中心的y座標
armor.angle = (vEllipse[nI].angle + vEllipse[nJ].angle) / 2; //裝甲所在旋轉矩形的旋轉角度
if (180 - dAngle < T_ANGLE_THRE)
armor.angle += 90;
nL = (vEllipse[nI].size.height + vEllipse[nJ].size.height) / 2; //裝甲的高度
nW = sqrt((vEllipse[nI].center.x - vEllipse[nJ].center.x) * (vEllipse[nI].center.x - vEllipse[nJ].center.x) + (vEllipse[nI].center.y - vEllipse[nJ].center.y) * (vEllipse[nI].center.y - vEllipse[nJ].center.y)); //裝甲的寬度等於兩側LED所在旋轉矩形中心座標的距離
if (nL < nW)
{
armor.size.height = nL;
armor.size.width = nW;
}
else
{
armor.size.height = nW;
armor.size.width = nL;
}
vRlt.push_back(armor); //將找出的裝甲的旋轉矩形保存到vector
}
}
}
return vRlt;
}
void drawBox(RotatedRect box, Mat img)
{
Point2f pt[4];
int i;
for (i = 0; i<4; i++)
{
pt[i].x = 0;
pt[i].y = 0;
}
box.points(pt); //計算二維盒子頂點
line(img, pt[0], pt[1], CV_RGB(0, 0, 255), 2, 8, 0);
line(img, pt[1], pt[2], CV_RGB(0, 0, 255), 2, 8, 0);
line(img, pt[2], pt[3], CV_RGB(0, 0, 255), 2, 8, 0);
line(img, pt[3], pt[0], CV_RGB(0, 0, 255), 2, 8, 0);
}
2. 關於omp.h頭文件
OpenMp提供了對並行算法的高層的抽象描述,程序員通過在源代碼中加入專用的pragma來指明自己的意圖,由此編譯器可以自動將程序進行並行化,並在必要之處加入同步互斥以及通信。
要在Visual C++ 中使用OpenMP其實不難,只要將 Project 的Properties中C/C++裏Language的OpenMP Support開啓(參數爲 /openmp),就可以讓VC++2005 在編譯時支持OpenMP 的語法了;而在編寫使用OpenMP 的程序時,則需要先include OpenMP的頭文件:omp.h。
在for循環前面添加如下代碼,可實現並行計算。
omp_set_num_threads(NUM_THREADS); //NUM_THREADS爲並行計算的線程數
#pragma omp parallel for
3. 程序流程分析
首先檢測出LED燈帶的位置,檢測的方法:圖像二值化,提取輪廓,擬合成旋轉矩形,判斷旋轉矩形的中心是否接近白色,若接近白色,則說明是LED等待所在位置,然後利用檢測出的LED燈帶的旋轉矩形的是否是平行關係來檢測裝甲。
一幀圖像爲例,說明其檢測過程,
原圖片
bo_test\robo_test\原圖片.png)
亮度調整。每個通道的數值-120,小於零=0,大於255則=255,用於突出LED燈帶所在區域
二值化。將上圖RGB,三個通道分離,R值減去G值,若大於25,則在二值圖像中爲255,否則爲0.
二值化後可以看到斷斷續續的LED燈區域的輪廓
膨脹之後(下圖),將輪廓較爲平滑,連通區域變得粗重。
腐蝕操作後見下圖,先膨脹在經腐蝕相當於閉運算,有去除空洞,使得輪廓變得平滑的功能
提取輪廓後
遍歷所有輪廓,找出滿足條件(輪廓要大於10個像素點)的輪廓,擬合成旋轉矩形,然後在判斷該旋轉矩形的中心
下一步是檢測裝甲的位置,在以上旋轉矩形中,兩兩判斷,根據下載原則定位裝甲:
- 兩個矩形近似平行,即旋轉矩形的角度之差接近
0o 或180o - 兩個旋轉矩形的寬和高應該相差不大。
然後求出裝甲的寬度和高度,高度取LED旋轉矩形height的平均值,寬度等於兩個旋轉矩形的中心距離。最後標出裝甲位置
4. 相關數據結構
4.1 旋轉矩形
//opencv2.4.x版本中的旋轉矩形類
CvBox2D
Public Member Functions //成員函數
CvBox2D (CvPoint2D32f c=CvPoint2D32f(), CvSize2D32f s=CvSize2D32f(), float a=0)
CvBox2D (const cv::RotatedRect &rr)
operator cv::RotatedRect () const
Public Attributes //成員變量
float angle
CvPoint2D32f center
CvSize2D32f size
//opencv3.0以上版本中的旋轉矩形類
RotatedRect
Public Member Functions //成員函數
RotatedRect () //無參數構造函數
RotatedRect (const Point2f ¢er, const Size2f &size, float angle)//構造函數
RotatedRect (const Point2f &point1, const Point2f &point2, const Point2f &point3)
Rect boundingRect () const//獲取包圍旋轉矩形的最小的直立矩形
void points (Point2f pts[]) const//獲取旋轉矩形的四個定點座標
Public Attributes //成員變量
float angle//旋轉角度
Point2f center//中心座標
Size2f size//尺寸
5. 相關函數
5.1 通道分離
//
//opencv2.4.x版本中的通道分離函數
void cvSplit ( const CvArr * src,
CvArr * dst0,
CvArr * dst1,
CvArr * dst2,
CvArr * dst3
)
//opencv3.0以上版本中的通道分離函數
void cv::split ( const Mat & src,
Mat * mvbegin //輸出的Mat數組
)
5.2 膨脹、腐蝕
//opencv2.4.x版本中的膨脹函數
void cvDilate ( const CvArr * src,
CvArr * dst,
IplConvKernel * element = NULL,
int iterations = 1
)
//opencv3.0以上版本中的膨脹函數
void cv::dilate ( InputArray src,
OutputArray dst,
InputArray kernel,//膨脹所用的結構元,默認爲3*3的矩形
Point anchor = Point(-1,-1),//結構元的中心
int iterations = 1,//膨脹操作的次數
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
//opencv2.4.x版本中的腐蝕函數
void cvErode ( const CvArr * src,
CvArr * dst,
IplConvKernel * element = NULL,
int iterations = 1
)
//opencv3.0以上版本中的腐蝕函數
void cv::erode ( InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
5.3 提取輪廓
//opencv2.4.x版本中的提取輪廓函數
int cvFindContours ( CvArr * image,
CvMemStorage * storage,
CvSeq ** first_contour,
int header_size = sizeof(CvContour),
int mode = CV_RETR_LIST,
int method = CV_CHAIN_APPROX_SIMPLE,
CvPoint offset = cvPoint(0, 0)
)
//opencv3.0以上版本中的提取輪廓函數
void cv::findContours ( InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
//注意該函數會修改輸入的二值化圖像,必要時要做備份
5.4 擬合旋轉矩形
//opencv2.4.x版本中的擬合旋轉矩形函數
CvBox2D cvFitEllipse2 ( const CvArr * points )
//opencv3.0以上版本中的擬合旋轉矩形函數
RotatedRect cv::fitEllipse ( InputArray points )
5.5 獲取指定點的像素
//opencv2.4.x版本中的獲取指定位置處像素的函數
CvScalar cvGet2D ( const CvArr * arr,
int idx0,
int idx1
)