一、需求提出
1、发文初衷
在贴片机研发过程中,一直有一种非常小众的需求,即散料识别。正好本人为一家公司定制过散料识别核心算法,基于opencv的开源精神,本人将代码的开源,希望对相同需求的开发有抛砖引玉的作用。
2、需求背景
贴片机一般使用元件料为编带封包。使用喂料器固定栈位贴装。散料应用场景,一般要除去规律包装,PCB打样,或者军品等二次筛选高品质元件,多数为人工贴装,其中的不可控因素可想而知。只要使用视觉算法,将元件的位置告诉贴片机,贴片机即可拾取贴装。
3、算法应用场景
同规格散料,非堆叠分布在托盘中,视觉识别这些元件,相对非常容易,而且能覆盖大部分的现场需求。
二、opencv算法过程
1、opencv版本为3.4,编程环境为QT5,编程平台为win10
2、识别结果
如图所示,1K-0805电阻,分散于白色面上,最终视觉识别出电阻的中心位置和短边角度,这样贴片机就可以准确拾取并贴装。
3、算法过程
2.1 首先将要识别的图片读入、并转换为灰度图片,以便后续高效处理
cv::Mat imgdata = cv::imread("bulk.jpg",IMREAD_COLOR);
cv::Mat grayImgdata;
cv::cvtColor(imgdata,grayImgdata,COLOR_BGR2GRAY);
2.2 图形双边滤波,去除图像中噪点,但保留边缘特征
cv::Mat bilateralMat;
cv::bilateralFilter(grayImgdata,bilateralMat,3,10,10);
具体效果如图所示,肉眼基本分辨不出图像改善,但对于图像噪点较多时,此步骤将特别有用
2.3 图像二值化,将图形处理为二值化图形,更方便识别轮廓,方便提取位置
cv::Mat threshold1;
cv::threshold(bilateralMat,threshold1,thresh1,255,THRESH_BINARY_INV);
图像二值化以后,基本的电阻位置已经非常明显,但在电阻的周围,我们发现有许多斑斑点点,还有“102”字样也需要处理
2.4 腐蚀与膨胀操作
cv::Mat morphologyMat;
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
//cv::morphologyEx(threshold1,morphologyMat,MORPH_CLOSE,element);
cv::erode(threshold1,morphologyMat,element);//侵蚀
element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4, 4));
cv::dilate(morphologyMat,morphologyMat,element);//扩展
图中可以看到,电阻周围的斑点,电阻的规格“102”字符,基本处理干净,个别电阻上偶尔还会留有黑色斑点,还需进一步处理
2.5 中值滤波
cv::Mat median;
cv::medianBlur(morphologyMat,median,3);
中值滤波以后,图形已经非常完美,每个电阻位置分明,电阻位置清晰可见
2.6 轮廓查找
std::vector<std::vector<Point>> contours;
std::vector<Vec4i> hierarchy;
cv::findContours(median, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);
如图所示,通过轮廓识别,所有的电阻已经被成功圈出,其中也包括一个圆形的人为干扰项
2.7 通过轮廓面积,过滤不符合规定的元件
// 按面积过滤
std::vector<std::vector<Point> > resultContours;
int areaMax = 1700;
int areaMin = 1100;
for (quint64 i=0; i<contours.size(); i++) {
//轮廓面积
double area = cv::contourArea(contours.at(i));
if(area >= areaMin && area <= areaMax){
resultContours.push_back(contours.at(i));
}
}
因为元件的每种封装大小,对应图像中的面积不同,图中0805对应的面积大约在1100-1700范围内。对于一台准备售出的贴片机而言,相机的分辨率,相机安装参数等,都已经固定,在封装设置时,可以根据实际情况,调整每种封装的面积范围,达到过滤的目的。
2.8 轮廓的位置查找
for(quint64 i=0;i<resultContours.size();i++){
RotatedRect rrect = cv::minAreaRect(resultContours.at(i));
drawRotatedRectMark(colorImgdata,rrect);
}
opencv的minAreaRect函数,可以帮助我们找到最小外接的矩形,此矩形中记录这元件的中心位置和角度信息,本例调用自处理函数drawRotatedRectMark(),对位置角度信息进行绘制显示
2.9 最小外接矩形处理与绘制
drawRotatedRectMark()代码
void drawRotatedRectMark(const Mat &imgdata, RotatedRect &rrect)
{
// 画出角度矩形
Point2f points[4];
rrect.points(points);
for (int j = 0; j < 4; ++j) {
cv::line(imgdata, points[j], points[(j + 1) % 4], Scalar(200,100,200), 2);
}
//画出中心点
cv::circle(imgdata, rrect.center, 3, Scalar(200,200,200), 2);
double markAngle;
//规范 width 要大于 height
if(rrect.size.width < rrect.size.height){
rrect.angle += 90;
float tmp = rrect.size.width;
rrect.size.width = rrect.size.height;
rrect.size.height = tmp;
}
markAngle = rrect.angle - 90.0;
//画出方向
cv::line(imgdata,
rrect.center,
Point2f(rrect.center.x + 1.5 * rrect.size.height / 2.0 * cos(markAngle*3.1416/180),
rrect.center.y + 1.5 * rrect.size.height / 2.0 * sin(markAngle*3.1416/180)),
Scalar(255,255,100),
2);
}
由于cpencv算法的原因,还需要对长短边进行进一步处理,本例中处理完成后,直接绘制了元件的中心和短边角度指示
三、总结
从文中可以得出,opencv对散料图形的处理实际非常简单,而且这种方式对贴片机元件的处理逻辑,基本相同,只需根据贴片机实际情况,设置一次封装的面积大小,可以达到识别所有散料的目的,通用行非常强。