本次笔记的内容主要是实现对形状的检查,使用Opencv中的函数实现图片中图形的监测。
1、首先开始对原始图形进行预处理。
String path="../shapes.png";
Mat img;
img=imread(path);
cvtColor(img,imgGray,COLOR_BGR2GRAY);
GaussianBlur(imgGray,imgBlur,Size(3,3),3,0);
Canny(imgBlur,imgCanny,25,75);
Mat kernel=getStructuringElement(MORPH_RECT,Size(3,3));
dilate(imgCanny,imgDil,kernel);
程序的开始首先开始读取图形,之后将图形转换为灰度图象。之后将图片模糊化。之后使用Canny算法查找图片的边缘。再使用dilate将监测到边缘进行膨胀放大。之后就完成对图形的初步处理。
2、构建一个函数对预处理的图形进行形状的识别
开始对轮廓的监测可以使用findContours()函数进行识别
findContours() [1/2]
void cv::findContours ( InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
本函数可以实现在二值图像中查找轮廓。
该函数使用算法从二进制图像中检索轮廓。轮廓是形状分析和对象检测和识别的有用工具。
image
源,8 位单通道图像。非零像素被视为 1。零像素保持为 0,因此图像被视为二进制。您可以使用compare、inRange、threshold、adaptiveThreshold、Canny等从灰度或彩色图像中创建二值图像。如果 mode 等于RETR_CCOMP或RETR_FLOODFILL,则输入也可以是标签的 32 位整数图像 (CV_32SC1)。
contours
检测到的轮廓。每个轮廓都存储为点向量(例如 std::vector<std::vector<cv::Point> >)。
hierarchy
可选的输出向量(例如 std::vector<cv::Vec4i>),包含有关图像拓扑的信息。它具有与轮廓数一样多的元素。对于每个第 i 个轮廓 contours[i],元素 hierarchy[i][0] 、 hierarchy[i][1] 、hierarchy[i][2] 和 hierarchy[i][3] 设置为 0-基于同一层级的下一个和前一个轮廓的轮廓中的索引,分别是第一个子轮廓和父轮廓。如果轮廓 i 没有下一个、上一个、父级或嵌套轮廓,则 hierarchy[i] 的相应元素将为负数。
mode
轮廓检索模式,请参阅RetrievalModes
method
轮廓逼近方法,见ContourApproximationModes
offset
每个轮廓点移动的可选偏移量。如果从图像 ROI 中提取轮廓,然后应该在整个图像上下文中对其进行分析,这将很有用。
其中contours就是我们要得出的轮廓,它是以二位vector的形式存储是的类型是point类,
相当与是二位点连成的图片
。
通过上面的图形可以看出会监测到多个图形的轮廓,但你会发现其中有一个黑点不是我们想要监测的形状。所以我们需要计算出每个轮廓的面积。可以使用contourArea()函数计算轮廓的面积,再通过面积的大小来过滤不需要的轮廓。
double cv::contourArea ( InputArray contour,
bool oriented = false
)
计算轮廓区域。
该函数计算轮廓区域。与矩 类似,面积是使用格林公式计算的。因此,如果您使用drawContours或fillPoly绘制轮廓,则返回的区域和非零像素的数量可能不同。此外,对于具有自相交的轮廓,该函数肯定会给出错误的结果。contour这也是一个二位的vector对象类型为point。程序中我们可以看出面积大于1000时为我们需要检测的轮廓。
arcLength()
double cv::arcLength ( InputArray curve,
bool closed
)计算轮廓周长或曲线长度。
该函数计算曲线长度或闭合轮廓周长。
approxPolyDP函数
以指定精度逼近多边形曲线。
函数cv::approxPolyDP用另一个具有较少顶点的曲线/多边形来逼近一条曲线或多边形,以使它们之间的距离小于或等于指定的精度。使用该函数是为了自创一个曲线来逼近形状的轮廓,比如长方形就用4个点来逼近轮廓,圆的话则需要6个以上点来逼近。
approxPolyDP()
void cv::approxPolyDP ( InputArray curve,
OutputArray approxCurve,
double epsilon,
bool closed
)
曲线 存储在 std::vector 或Mat中的 2D 点的输入向量
近似曲线 近似的结果。该类型应与输入曲线的类型相匹配。
ε 指定近似精度的参数。这是原始曲线与其近似值之间的最大距离。
关闭 如果为真,则近似曲线是闭合的(它的第一个和最后一个顶点是连接的)。否则,它不会关闭。
boundingRect()
Rect cv::boundingRect ( InputArray array )计算点集或灰度图像的非零像素的右上边界矩形。
该函数计算并返回灰度图像的指定点集或非零像素的最小上边界矩形。返回一个矩形类。
其中rect类中的tl和br为矩形的左上角和右下角。
void getContours(Mat imgDil,Mat img)
{
vector< vector<Point> > contours; //用来存储识别到的轮廓
vector<Vec4i> hierarchy;
string objectType;//识别出的形状名称
//开始识别,将识别出的轮廓存在contours
findContours(imgDil,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
/* drawContours(img,contours,-1,Scalar(255,0,255),10); */
//循环次数为查找出轮廓的个数
for(int i=0;i<contours.size();i++)
{
int area =contourArea(contours[i]); //计算出每个轮廓的面积
cout<<"area="<<area<<endl; //输出每个轮廓的面积
vector< vector<Point> > conPoly(contours.size()); //近似的轮廓,有几个点连成
vector<Rect> boundRect(contours.size()); //存储筛选出的矩形轮廓
if(area>1000) //筛选面积大的进行处理,小噪点则滤出。
{
float peri =arcLength(contours[i],true); //计算出轮廓的长度
//使用轮廓长度的0.02倍数为精度近似出轮廓出来,这样可以减少轮廓点
approxPolyDP(contours[i],conPoly[i],0.02*peri,true);
//找出轮廓为矩形的轮廓
boundRect[i]=boundingRect(conPoly[i]);
cout<<conPoly[i].size()<<endl;
int objCor =(int )conPoly[i].size();//将近似出的轮廓点个数传递
//近似出的轮廓点可以判读出形状为3个点为三角形,以此类推
if(objCor==3)
{
objectType="Tri";
}
else if(objCor==4)
{
//判读是否为正方形,计算出矩形的长宽比值
float aspRatio =(float)boundRect[i].width/(float)boundRect[i].height;
if(aspRatio>0.95&&aspRatio<1.05)
{
objectType="Square";
}else
{
objectType="Rect";
}
}
else if(objCor>4){objectType="Circle";}
putText(img,objectType,{boundRect[i].x,boundRect[i].y-5},FONT_HERSHEY_PLAIN,1,Scalar(100,5,12),1);
//其中rect类中的tl和br为矩形的左上角和右下角。
rectangle(img,boundRect[i].tl(),boundRect[i].br(),Scalar(0,255,0),5);
drawContours(img,conPoly,i,Scalar(255,0,255),3);
}