C++实现钢管计数

来源:互联网 发布:网络主播思瑞护士视频 编辑:程序博客网 时间:2024/04/29 22:27
目前钢管计数主要采用人工计数,既繁琐又易错,同时在实际生产中,由于实际尺寸与理论设计之间存在一定的误差, 如果按照重量计算,会造成一定的销售成本损失, 因此实现对棒材的生产成品进行准确计数具有重要的现实意义。 本算法主要针对捆扎钢管, 拍摄钢管截面图像(尽量减少遮挡),通过识别钢管内部阴影的个数实现钢管辅助计数。由于依赖二值化图像的质量,所以对光照要求较高,存在一定的误识别, 算法主要分为两个部分:
1. 图像二值化。也就是得到质量良好的截面区域。使用带有上下限(需根据场景手动设置)的二值化方法,去除深色背景(Threshhold1)和除钢管阴影以外的亮区域(Threshhold2)对ROI(感兴趣) 区域的干扰。
2. 提取目标连通区域轮廓。 经形态学开运算处理后,去除噪声点和独立小点特大区域的干扰, 分割出目标连通区域后, 提取所有连通区域的边界点。 其中连通区域的面积选择根
据经验设置。
3. 统计连通区域的圆特性。以连通区域的最小外接圆半径作为期望没计算轮廓上个点到圆心距离的方差来确定是否为类圆。 方差可信度的范围根据图像的效果进行手动设置。

下面是实现的代码:

// selectshape.cpp : 选择轮廓
//二值化的结果是识别的关键,提取出所有圆形区域,后面通过调节类圆的方差阈值,连通区域的大小可以提高识别率
#include "stdafx.h"
#include <iostream>


#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"


#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
#define  VP  vector<Point>  //用VP符号代替 vector<point>
RNG  rng(12345);
//带有上下限的threshold
void threshold2(Mat gray,Mat& thresh,int minvalue,int maxvalue)
{
Mat thresh1;
Mat thresh2;
//二值化 阈值为43
//THRESH_BINARY  当前点值大于阈值时,取Maxval,否则设置为0
threshold(gray,thresh1,43,255, THRESH_BINARY);
//?
//THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
threshold(gray,thresh2,111,255,THRESH_BINARY_INV);
thresh = thresh1 & thresh2;
}


//寻找并绘制出联通区域-----------------------------------------------------------------------此处出现大部分错误联通区域导致圆识别失败
vector<VP> connection2(Mat src,Mat& draw)
{    
draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
vector<VP>contours;    
// —CV_RETR_LIST:检测所有轮廓,但不建立继承(包含)关系。
// – CV_CHAIN_APPROX_SIMPLE:只存储水平,垂直,对角直线的起始点。对drawContours函数来说,这两种方法没有区别。
findContours(src,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);


for (int i=0;i<contours.size();i++)
{
Scalar  color  = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
drawContours(draw,contours,i,color,-1);
}
return contours;
}
//根据面积提取连通区域select_shape,返回轮廓
vector<VP>  selectShapeArea(Mat src,Mat& draw,vector<VP> contours,int minvalue,int maxvalue)
{
vector<VP> result_contours;
draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
for (int i=0;i<contours.size();i++)

//计算整个轮廓的面积
int countour_area = contourArea(contours[i]);
if (countour_area >minvalue && countour_area<maxvalue)
{
//初始化?
result_contours.push_back(contours[i]);
}
}
for (int i=0;i<result_contours.size();i++)
{
Scalar  color  = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
drawContours(draw,result_contours,i,color,-1);
}
return result_contours;


}
//计算轮廓的圆的特性
float calculateCircularity(VP contour)
{
Point2f center;
float radius = 0;
minEnclosingCircle((Mat)contour,center,radius);
//以最小外接圆半径作为数学期望,计算轮廓上各点到圆心距离的方差
float fsum = 0;
float fcompare = 0;
for (int i=0;i<contour.size();i++)
{   
//轮廓上各点
Point2f ptmp = contour[i];
//点到圆心距离差
float fdistenct = sqrt((float)((ptmp.x - center.x)*(ptmp.x - center.x)+(ptmp.y - center.y)*(ptmp.y-center.y)));
float fdiff = abs(fdistenct - radius);
fsum = fsum + fdiff*fdiff;
}


//各点到圆心距离的方差
fcompare = fsum/(float)contour.size();
return fcompare;
}


//选择类圆区域selectShapeCircularity
vector<VP> selectShapeCircularity(Mat src,Mat& draw,vector<VP> contours,float minvalue,float maxvalue)
{
vector<VP> result_contours;
draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
for (int i=0;i<contours.size();i++)
{
float fcompare = calculateCircularity(contours[i]);
if (fcompare >=minvalue && fcompare <=maxvalue)
{
result_contours.push_back(contours[i]);
}
}


//将选出的类圆区域随机上蓝
for (int i=0;i<result_contours.size();i++)
{
Scalar  color  = Scalar(255,0,0);
drawContours(draw,result_contours,i,color,-1);
}
return result_contours;
}






int _tmain(int argc, _TCHAR* argv[])
{
Mat src;
Mat drawCircleOnTheOriginal;
Mat gray;
Mat thresh;
Mat draw_connection;
Mat contours_area_open;
Mat contours_area_close;
Mat draw_open;
Mat draw_close;
Mat draw_area;
Mat draw_circle;
vector<VP>contours_connection;    
vector<VP>contours_area;
vector<VP>contours_circle;
vector<VP>contours_tmp;


//read_image (Image1, 'F:/未来项目/钢管识别/FindTube/FindTube/1.jpg')
src = imread("1.jpg");


 
//rectangle(src,rect,Scalar(0,0,0),-1);


//rgb1_to_gray (Image1, GrayImage)
cvtColor(src,gray,COLOR_BGR2GRAY);
//threshold (GrayImage, Regions, 43, 111)
threshold2(gray,thresh,43,111);






//开闭运算Size(10, 10)可根据圆形区域大小改变
Mat seOpen = getStructuringElement(MORPH_CROSS, Size(10, 10));
morphologyEx(thresh.clone(), contours_area_open, MORPH_OPEN, seOpen);
//imshow("draw_open.jpg", contours_area_open);
/*Mat seClose = getStructuringElement(MORPH_CROSS, Size(3, 3));
morphologyEx(contours_area_open, contours_area_close, MORPH_CLOSE, seClose);
imshow("draw_close.jpg", contours_area_close);*/ 








//connection (Regions, ConnectedRegions)
contours_connection = connection2(contours_area_open, draw_connection);

//select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 150, 666)
//根据不同的场景修改区域大小的阈值130,666
double areaMin = 130;
double areaMax = 600;
contours_area = selectShapeArea(thresh.clone(), draw_area, contours_connection, areaMin, areaMax);
//select_shape (SelectedRegions, SelectedRegions1, 'circularity', 'and', 0.45, 1)
//根据不同的场景修改方差的范围,或者根据统计到的最小值的倍数确定
double believableValue = 25;
contours_circle = selectShapeCircularity(thresh.clone(), draw_circle, contours_area, 1, believableValue);

//dll


for (int i = 0; i<contours_circle.size(); i++)
{
Point2f center;
float   radius;
minEnclosingCircle(contours_circle[i], center, radius);
if (radius > 10 * 0.8 && radius < 10 * 1.4)
{


//minEllipse   9.25
if (ellipseLikeCircle(minEllipse[i]))
{
circle(src, center, radius, Scalar(0, 0, 255));
//ellipse(  src,minEllipse[i],Scalar(255,0,0));
pair<Point2f, float> tmp;
tmp.first = center;
tmp.second = radius;
contours_area.push_back(tmp);
}
}
}


//在原图上显示识别出的钢管
drawCircleOnTheOriginal = draw_circle + src;
imshow("draw_circleOnTheOriginal", drawCircleOnTheOriginal);
imwrite("draw_circleOnTheOriginal.jpg", drawCircleOnTheOriginal);
waitKey();
}

原创粉丝点击