我毕业设计做的是芯片引脚缺陷检测,用的是halcon+MFC,其实说白了,就是将halcon的程序进行导出、拆分,嵌入到mfc各个部件中,从而实现整体功能。因为研一上学期学的其实是opencv+qt5,所以这部分不是很熟。这里主要将我做的毕设的过程,以及在这过程中遇到的问题记录下来,留作以后参考,也算是经验的积累。
描述一:在检测引脚平面度的时候,需要将引脚底部中心点画出来,可利用halcon助手的基于互相关的模板匹配例子,导出后可拆分成打开原图、设定模板、匹配图三个部分,但在第三个匹配图中我想直接把halcon里面的关于引导中心点十字光标显示出来,如图:
但这部分程序(如下)只有输出图(参数一),各中心的坐标(hv_MatchingCol为x坐标, hv_MatchingRow为y坐标),但是没有句柄,自己创建一个句柄有错误,不知道可不可以直接显示输出图(有大神知道的可以告知一下,感激不尽)。
GenCrossContourXld(&ho_TransContours, hv_MatchingRow, hv_MatchingCol, 20, hv_MatchingAngle);
最后没有办法,只能通过上边的坐标直接通过画笔挨个把点画出来。
问题一:如何获取引脚中心点的坐标?
解决:
上边的GenCrossContourXld()函数给出了 HTuple类型的中心点行列坐标,需要将HTuple类型转换成int/double类型的。
问题二:如何把坐标点画到图片上(画笔、画刷的用法),而不是图片所在的控件上?
解决:
为你图片所在的控件添加一个变量,比如m_pic,然后在OnPaint()中加入
//调用实现图片打开功能的函数 OnBnClickedButton2(); //例:在图片上画一个圆 CDC *pDC = m_pic.GetWindowDC(); CPen pen(PS_SOLID,6,RGB(255,0,0)); pDC->SelectObject(&pen); pDC->Ellipse(50,50,101,101);
描述二:当时可以将点画在图片上面了,但是当点比较大的时候,需要将图片所在的控件放大,才能显示在图片上,所以我猜测坐标可能是相对于图片的像素大小而言的,也就是像素坐标,要想在mfc控件上显示准确的坐标,一种方法是将控件放大到和图片像素大小一下(显然不靠谱),而另一种方法就需要将坐标进行一个缩放了。
问题三:如何将相对于图片像素大小的坐标,进行缩放,准确显示到Picture Control上的图片上?
解决:
当不进行缩放时,在原始图片上显示的点坐标,
到了mfc对话框中,可能会显示不出来(控件太小了),将控件放大后,可能会在任何位置显示,不会准确显示到引脚中心,所以需要将坐标进行缩放,依据的原理是:
只需要将相对于图片大小的坐标缩放到相对于控件大小的即可,可通过公式控件宽/图片宽=x“/x=y”/y, 即可求出缩放后的坐标(x“,y”),而图片宽,控件宽能求,(x,y)已知。
代码:
//获取控件尺寸和位置 CRect rectCtrl; CStatic *p = (CStatic*)GetDlgItem(IDC_STATIC3); //p->MoveWindow(100, 100, 100, 100);//更改控件大小并移动其到指定位置 p->GetWindowRect(rectCtrl); this->ScreenToClient(rectCtrl); //GetDlgItem(IDC_STATIC_TEST)->GetClientRect(rectCtrl); int width = rectCtrl.Width(); int height = rectCtrl.Height(); OnBnClickedButton1(); //获取图片尺寸(pic为图片的类型) int cx = pic.GetWidth(); int cy = pic.GetHeight(); OnBnClickedButton2(); CDC *pDC = m_pic.GetWindowDC(); CPen pen(PS_SOLID,4,RGB(255,0,0)); pDC->SelectObject(&pen); //point.x ==col //point.y ==row int i=0; int j=0; for(;i<5,j<5;i++,j++) { double ti = hv_MatchingCol[i].D(); double tj = hv_MatchingRow[j].D(); pDC->Ellipse((ti/cx)*width,(tj/cy)*height,(ti/cx)*width+1,(tj/cy)*height+1); }
效果:
问题四:c++ 如何将多个点存入容器?
描述:我说的将多个点存入容器,不是自己手动一个点一个点的存,那样感觉很蠢,而且换一张和之前点数不一样的图片后就不行了,不具有普遍性。
解决:
首先定义一个结构:
struct Point { double x; double y; };
然后,没改之前的程序是下面这样的,
vector<Point> points; Point point1; for(int i = 0; i<5;i++) { point1.x=hv_MatchingCol[i].D(); point1.y=hv_MatchingRow[i].D(); points.push_back(point[i]); }
因为point1.x,point1.y的值在不断更新,这就导致容器points里面存入的值不断被新进来的值给替换掉,而不是都存入容器points中。
解决办法就是point.x与hv_MatchingCol[i].D(),point.y与hv_MatchingRow[i].D();的值一一对应,而不是前面的值一直在更新。
改进之后的程序:
vector<Point> points; Point point[5]; for(int i = 0; i<5;i++) { point[i].x=hv_MatchingCol[i].D(); point[i].y=hv_MatchingRow[i].D(); points.push_back(point[i]); }
注:halcon 里面HTuple类型的 hv_MatchingCol,hv_MatchingCol里面之前就各有五个数,而hv_MatchingCol[i].D()是将HTuple类型转换为double类型。
问题五:将多个点拟合成一条直线,并求出点到直线的最大距离和最小距离。
参考:https://blog.csdn.net/liyuanbhu/article/details/50866802
描述:
- 最小二乘法直线拟合(不是常见的一元线性回归算法)
- 将离散点拟合为 a x + b y + c = 0 型直线
- 假设每个点的 X Y 坐标的误差都是符合 0 均值的正态分布的。
- 与一元线性回归算法的区别:一元线性回归算法假定 X 是无误差的,只有 Y 有误差。
解决:
可以与上面存入点的容器对接,利用已知点,求出直线方程中的系数a,b,c。
double a, b, c; int size = points.size(); if(size < 2) { a = 0; b = 0; c = 0; } double x_mean = 0; double y_mean = 0; for(int i = 0; i < size; i++) { x_mean += points[i].x; y_mean += points[i].y; } x_mean /= size; y_mean /= size; //至此,计算出了 x y 的均值 double Dxx = 0, Dxy = 0, Dyy = 0; for(int i = 0; i < size; i++) { Dxx += (points[i].x - x_mean) * (points[i].x - x_mean); Dxy += (points[i].x - x_mean) * (points[i].y - y_mean); Dyy += (points[i].y - y_mean) * (points[i].y - y_mean); } double lambda = ( (Dxx + Dyy) - sqrt( (Dxx - Dyy) * (Dxx - Dyy) + 4 * Dxy * Dxy) ) / 2.0; double den = sqrt( Dxy * Dxy + (lambda - Dxx) * (lambda - Dxx) ); if(fabs(den) < 1e-5) { if( fabs(Dxx / Dyy - 1) < 1e-5) //这时没有一个特殊的直线方向,无法拟合 { } else { a = 1; b = 0; c = - x_mean; } } else { a = Dxy / den; b = (lambda - Dxx) / den; c = - a * x_mean - b * y_mean; } CDC *pDC1 = m_pic.GetWindowDC(); CPen pen1(PS_SOLID,1,RGB(0,0,255)); pDC1->SelectObject(&pen1); pDC1->MoveTo((points.at(0).x)/cx*width,-((a*points.at(0).x+c)/b)/cy*height); pDC1->LineTo((points.at(4).x)/cx*width,-((a*points.at(4).x+c)/b)/cy*height);
效果:
求出点到直线的最大距离和最小距离:
//求出引脚中心点到标注直线的距离,并存入容器dis中 vector<double>dis; for(int i =0;i<size;i++) { double d1 = a * points.at(i).x+b * points.at(i).y+c; double d2 = sqrt(a*a+b*b); double d = sqrt(d1/d2*d1/d2); dis.push_back(d); } //求出距离最大值,最小值 double min,max; max = min = dis.at(0); for (int i = 0; i < size; i++) { if (max < dis.at(i)) max = dis.at(i); if (min > dis.at(i)) min = dis.at(i); }
问题六:在MFC中Edit控件上显示含小数点的数,并可以控制小数点后的位数。
以double类型小数0.274002149218631为例
解决:
方法一:精确到小数点后一位
1)首先为控件Edit添加一个CString类型的变量
2)添加代码:
CString s; s.Format("%.lg", max); //显示一位小数 max_edit =s; UpdateData(FALSE);
效果:
方法二:原来多少位的小数,就显示多少位的:
1)为编辑框Edit添加一个double类型的变量min_edit
2)添加代码:
min_edit = min; UpdateData(FALSE);//把控件关联的变量值“刷到”控件上显示
效果:
方法三:任意控制小数点后的位数:
1)为编辑框Edit添加一个double类型的变量min_edit
2)添加代码:
min_edit = max; min_edit = floor(min_edit * 1000.000f + 0.5) / 1000.000f; UpdateData(FALSE);//把控件关联的变量值“刷到”控件上显示
上面是精确到小数点后三位,如果精确到小数点后两位,改成100.000f。
效果:
问题七:打开多张图片后,第二张之后的所有图片显示的引脚中心点拟合出的直线都是第一张图的芯片引脚直线,如图:
解决:
之前一直奇怪为什么引脚中心点更新出来了,而画的直线没有更新出来,一直再找如何对画的图进行更新。后来仔细想了一下,发现第二次打开图片后,由于容器里面存放的第一张图片中芯片引脚的坐标点还在,再打开第二张图片后,虽然第二张图片的芯片引脚数据存到容器里面了,但是在第一张图片芯片引脚数据之后,所以画的还是第一张图片的直线,而画坐标点没有用到容器,将坐标提取出来就直接画了,自然没有问题。最后在画完直线后,将容器里芯片引脚中心坐标数据清空,问题解决。