基于Opencv的法兰盘螺纹孔位置确定(一)

汽车工业领域的法兰盘分布有螺纹孔,实际生产过程中,螺纹孔因加工问题未安上螺纹,本项目旨在对法兰盘螺纹孔定位并检测。


基于Opencv的法兰盘螺纹孔位置确定(一)_第1张图片
定位圆环ROI过程如下所示

一、Canny算子边缘检测得到边缘图

    cv::Mat cannyImg;
    int low_Val = 100, high_Val = 2 * low_Val;    //进行Canny边缘检测的高阈值与低阈值
    cv::Canny(image0, cannyImg, low_Val, high_Val, 3);    //Canny边缘检测参数,默认apertureSize = 3

基于Opencv的法兰盘螺纹孔位置确定(一)_第2张图片

二、闭运算

    Mat closeSmallImg;
    cv::Mat elementSmall = cv::getStructuringElement(cv::MORPH_ELLIPSE,
        cv::Size(3, 3)
    );
    //不用开运算会把轮廓去掉
    //不用9*9,9*9得到的螺纹孔更多,但背景的圆不易被剔除掉,后续思考改进
    cv::morphologyEx(cannyImg, closeSmallImg, cv::MORPH_CLOSE, elementSmall);
    //将小空隙填满,将临近的轮廓连接

基于Opencv的法兰盘螺纹孔位置确定(一)_第3张图片

三、轮廓跟踪寻找轮廓

    vector<cv::Vec4i> hierarchy;
    vector<vector<cv::Point> > contours;
    cv::findContours(closeSmallImg, contours, hierarchy,
        cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));

    Mat drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
    cv::RNG rng(12345);
    for (int i = 0; i < contours.size(); i++)
    {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        cv::drawContours(drawImg, contours, i, color, 2, 8, hierarchy, 0, cv::Point());
    }
    cv::imshow("s3 Contours Image", drawImg);

基于Opencv的法兰盘螺纹孔位置确定(一)_第4张图片

四、预设置参数

    //轮廓长度与轮廓间距离设置
    int minCDis = 50;    //筛选轮廓时,允许的最小轮廓间距离
    int minCircleLen = 65;    //筛选轮廓时定位圆孔的最小轮廓长度
    int maxCircleLen = 75;    //筛选轮廓时定位圆孔的最大轮廓长度
    //圆环半径设置
    float RH_mm = 140;    //检测圆盘圆孔的半径 单位:毫米
    float R0_mm = 115;   //检测圆盘的内径    单位:毫米
    float R1_mm = 160;  //检测圆环的外径    单位:毫米

    //二、标记ROI预览显示ROI
     cv::Point estimateCenter = cv::Point(750, 500);            //估计的ROI区域的中心坐标
    int estimateRadius = 500;                                //估计的ROI区域的半径
    Mat subImg = Mat::zeros(image0.size(), CV_8UC1);    //初始化一个mat,其大小与srcImg一样,类型为8位单通道类型    
    image0.copyTo(subImg);                                        //把src里的图像复制到subimg中
    cv::cvtColor(subImg, subImg, cv::COLOR_GRAY2RGB);            //把subimg转变为彩色图像
    //thickness为1,lineType为8,shift为对应给定点的小数位数(0对结果不产生影响)
    cv::circle(subImg, estimateCenter,
        estimateRadius, cv::Scalar(0, 0, 255), 1, 8, 0);    //在subimg上画圆
    cv::imshow("s0 ROI Img", subImg);

五、轮廓筛选

    const cv::Scalar RED = cv::Scalar(0, 0, 255);  
    const cv::Scalar GREEN = cv::Scalar(0, 255, 0);
    const cv::Scalar BLUE = cv::Scalar(255, 0, 0);
    const cv::Scalar YELLOW = cv::Scalar(0, 255, 255);
    //1.不在估计区域范围内的轮廓去掉,相同重心的轮廓只保留一个contours2
    // Get the moments,得到轮廓矩
    vector<cv::Moments> mu(contours.size());
    for (int j = 0; j < contours.size(); j++)
    {
        mu[j] = moments(contours[j], false);
    }

    ///  Get the mass centers,重心
    vector<cv::Point2f> mc(contours.size());
    for (int j = 0; j < contours.size(); j++)
    {
        mc[j] = cv::Point2f(mu[j].m10 / mu[j].m00, mu[j].m01 / mu[j].m00);
        std::cout << "xpos = " << mc[j].x << " ypos = " << mc[j].y << std::endl;
    }
    //开始筛选
    vector<vector<cv::Point>> contours2;
    vector<cv::Point2f> mc_finded;
    for (int i = 0; i < contours.size(); ++i)
    {
        cv::Point r_pos = mc[i];
        bool disFlag = true;
        //先检查轮廓出现的位置,计算轮廓矩重心的距离与估计圆心的距离
        float disR = calDistance2D(r_pos.x, r_pos.y, estimateCenter.x, estimateCenter.y);
        if (disR > estimateRadius)
        {    //在ROI区域外 则无需比较
            disFlag = false;
        }
        else
        {    //检查这个轮廓与已找到的轮廓中心是否很接近,如果是则排除 跳出循环
            for (int j = 0; j < mc_finded.size(); ++j)
            {
                cv::Point f_pos = mc_finded[j];
                float dis = calDistance2D(r_pos.x, r_pos.y, f_pos.x, f_pos.y);
                if (dis <= minCDis)        //轮廓中心之间的距离
                {
                    disFlag = false;
                    break;
                }
            }
        }
        if (disFlag)
        {    //此轮廓的中心点位置与前都不同
            mc_finded.push_back(mc[i]);
            contours2.push_back(contours[i]);
        }
    }
    drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
    for (int i = 0; i < contours2.size(); i++)
    {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        cv::drawContours(drawImg, contours2, i, color, 2);
    }
    cv::imshow("s4 reSelected Contours2 Image", drawImg);
    std::cout << " * Contour2 Size = " << contours2.size() << std::endl;


    //2.检查轮廓的长度contours3
    vector<float> contourLens;
    for (int i = 0; i < contours2.size(); ++i)
    {
        float conLen = arcLength(contours2[i], true);
        contourLens.push_back(conLen);
        std::cout << " * Contours Length = " << conLen << std::endl;
    }
    vector<vector<cv::Point>> contours3;
    for (int i = 0; i < contourLens.size(); ++i)
    {
        if ((contourLens[i] > 260) &&
            (contourLens[i] < 400))            //检查轮廓的周长参数
        {
            contours3.push_back(contours2[i]);
            //std::printf("Contours Idx = %d Length = %f\n", i, contourLens[i], true);
        }
    }
    drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
    for (int i = 0; i < contours3.size(); i++)
    {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        cv::drawContours(drawImg, contours3, i, color, 2);
    }
    cv::imshow("s5 reSelected Contours3 Image", drawImg);
    std::cout << " * Contour3 Size = " << contours3.size() << std::endl;


    //3.检查外接矩形 如果外接矩形的长和宽相差超过阈值 则删除contours4
    vector<vector<cv::Point>> contours4;
    {
        for (int i = 0; i < contours3.size(); i++)
        {
            cv::RotatedRect one_rRect = minAreaRect(contours3[i]);
            float jRes = abs(one_rRect.size.width - one_rRect.size.height);
            if (jRes < 10)    //检查轮廓外接矩形的长和宽
            {
                contours4.push_back(contours3[i]);
            }
        }
        drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
        for (int i = 0; i < contours4.size(); i++)
        {
            cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255),
                rng.uniform(0, 255));
            cv::drawContours(drawImg, contours4, i, color, 2);
        }
        cv::imshow("s6 check RECT Contours4 Image", drawImg);
        std::cout << " * Contour4 Size = " << contours4.size() << std::endl;
    }


    //4.检查轮廓上各点到矩形中心的距离 计算每个轮廓距离的方差 选择方差最小的N个
    vector<cv::Point2f> holeCenter;
    vector<float> holeRadius;
    int sCnt = 10;
    if (contours4.size() >= sCnt)
    {    //如果contours4的轮廓数大于N,则再从距离方差的角度筛选
        //依次计算各轮廓上各点到轮廓中心距离序列的方差
        std::vector<float> contours_variance(contours4.size());
        for (int i = 0; i < contours4.size(); i++)
        {
            //计算轮廓的最小外接圆中心点
            cv::Point2f circle_center;
            float circle_radius;
            minEnclosingCircle(contours4[i], circle_center, circle_radius);

            //计算最小外接圆中心点到轮廓上各点的距离
            std::vector<float> disList(contours4[i].size());
            for (int j = 0; j < contours4[i].size(); j++)
            {
                cv::Point pos = contours4[i][j];
                float dis = calDistance2D(pos.x, pos.y,
                circle_center.x, circle_center.y);
                disList[j] = dis;
            }

                //计算距离序列的方差
            float varianceDis = calVariance_1DLine(disList);
            contours_variance[i] = varianceDis;
            std::cout << contours_variance[i] << std::endl;
        }

        //选择方差最小的N个轮廓
        vector<vector<cv::Point>> contours4Clone(contours4);
        contours4.clear();
        for (int i = 0; i < sCnt; i++)
        {
            int minIdx = min_element(contours_variance.begin(), contours_variance.end()) - contours_variance.begin();    //在方差序列中找到最小值位置
            std::vector<float>::iterator idxIt = contours_variance.begin() + minIdx;    //方差序列中的迭代器
            std::vector<vector<cv::Point>>::iterator contoursIt = contours4Clone.begin() + minIdx;    //轮廓序列中的迭代器
            contours4.push_back(*contoursIt);    //把对应的轮廓存储到contours4中
            contours_variance.erase(idxIt);        //删除
            contours4Clone.erase(contoursIt);    //删除
        }
    }

    //contours4中的轮廓依次计算外接矩形 轮廓中心和半径并存储contours5
    vector<cv::Rect> boundRect;
    vector<vector<cv::Point>> contours5;
    holeCenter.clear();
    holeRadius.clear();

    for (int i = 0; i < contours4.size(); i++)
    {
        cv::Rect one_bRect;
        one_bRect = boundingRect(Mat(contours4[i]));
        cv::Point2f circle_center;
        float circle_radius;
        minEnclosingCircle(contours4[i], circle_center, circle_radius);
        boundRect.push_back(one_bRect);    //vector boundRect
        holeCenter.push_back(circle_center);
        holeRadius.push_back(circle_radius);
        contours5.push_back(contours4[i]);
    }
    /// Draw polygonal contour + bonding rects + circles
    drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);

    for (int i = 0; i < contours5.size(); i++) {
        cv::Scalar color = GREEN;
        drawContours(drawImg, contours5, i, color, 1, 8, vector<cv::Vec4i>(), 0, cv::Point());
        rectangle(drawImg, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);
        cv::circle(drawImg, holeCenter[i], 2, GREEN, 1, 8, 0);
        cv::putText(drawImg, std::to_string(i), holeCenter[i], cv::FONT_HERSHEY_SCRIPT_SIMPLEX, 1, cv::Scalar(0, 0, 255), 1);
    }
    cv::imshow("s7 selected Contours5 Image", drawImg);
    std::cout << " * Contour5 Size = " << contours5.size() << std::endl;

基于Opencv的法兰盘螺纹孔位置确定(一)_第5张图片

六、最小二乘法拟合圆

    //根据圆周上的多点 计算圆心
    cv::Point2f centerPos;
    float cirRadius;    //圆盘上孔到圆心的像素距离
    calCircleParas(holeCenter, centerPos, cirRadius);
    cv::circle(drawImg, centerPos, 2, cv::Scalar(0, 0, 255), 1, 8, 0);
    cv::circle(drawImg, centerPos, cirRadius, cv::Scalar(0, 255, 0), 1, 8, 0);
    cv::imshow("s8 selected Contours5 Image with circle", drawImg);

    //在grad上标记
    float R0_pixel, R1_pixel;    //涂胶圆盘的外径和内径 像素单位
    {
        float pm_Ratio = cirRadius / RH_mm;
        R0_pixel = pm_Ratio * R0_mm;
        R1_pixel = pm_Ratio * R1_mm;
    }
    Mat roiOut = cv::Mat::zeros(image0.size(), CV_8UC1);
    cv::circle(roiOut, centerPos, R1_pixel, cv::Scalar(255), -1, 8, 0);
    Mat roiIn = cv::Mat::zeros(image0.size(), CV_8UC1);
    cv::circle(roiIn, centerPos, R0_pixel, cv::Scalar(255), -1, 8, 0);
    Mat roi = roiOut - roiIn;
    Mat maskImg;
    image0.copyTo(maskImg, roi);//掩膜提取
    //保存图像后续***************************
    cv::imwrite("C:\\Users\\sunrise_oneday\\Desktop\\20221107\\Roi\\1.jpg", maskImg);
    //在src img上标记
    cv::Mat colorSrc = cv::Mat::zeros(image0.size(), CV_8UC1);
    image0.copyTo(colorSrc);
    cv::cvtColor(colorSrc, colorSrc, cv::COLOR_GRAY2RGB);
    cv::circle(colorSrc, centerPos, 2, RED, 1, 8, 0);
    cv::circle(colorSrc, centerPos, cirRadius, GREEN, 1, 8, 0);
    cv::circle(colorSrc, centerPos, R1_pixel, YELLOW, 1, 8, 0);
    cv::circle(colorSrc, centerPos, R0_pixel, YELLOW, 1, 8, 0);
    cv::imshow("s9 check on src Img", colorSrc);

基于Opencv的法兰盘螺纹孔位置确定(一)_第6张图片

七、一些功能函数

float calDistance2D(float x0, float y0, float x1, float y1)    //计算两点之间的距离
{
    float res = sqrtf(pow((x0 - x1), 2) + pow((y0 - y1), 2));
    return res;
}

float calAverageVal_1DLine(std::vector<float>& line)    //计算一维数组的平均值
{
    int cnt = line.size();

    float sum = 0;

    for (int i = 0; i < cnt; i++)
    {
        sum += line[i];
    }

    return (sum / cnt);
}

float calVariance_1DLine(std::vector<float>& line)    //计算一维数组的方差    
{
    float avaVal = calAverageVal_1DLine(line);

    int cnt = line.size();

    float sum = 0;
    for (int i = 0; i < cnt; i++)
    {
        sum += pow((line[i] - avaVal), 2);
    }

    float res = sum / (cnt - 1);

    return res;
}

void calCircleParas(std::vector<cv::Point2f> points,
    cv::Point2f& cirCenter,
    float& cirRadius)    //由圆周上的点来计算二维圆心与半径
{
    int n = points.size();
    cv::Mat dst = cv::Mat(n, 3, CV_32F, cv::Scalar(0));//初始化系数矩阵A
    cv::Mat out = cv::Mat(n, 1, CV_32F, cv::Scalar(0));//初始化矩阵b

    for (int i = 0; i < n; i++)
    {
        //计算n*3的系数矩阵
        dst.at<float>(i, 0) = points[i].x;
        dst.at<float>(i, 1) = points[i].y;
        dst.at<float>(i, 2) = -1.;
        //计算3*1的结果矩阵
        out.at<float>(i, 0) = pow(points[i].x, 2) + pow(points[i].y, 2);
    }

    cv::Mat output;// = inv * out;//计算输出
    //cv::Mat mM;
    cv::solve(dst, out, output, cv::DECOMP_QR); //, CV_QR CV_SVD );

    float a = output.at<float>(0, 0);
    float b = output.at<float>(1, 0);
    float c = output.at<float>(2, 0);
    cirCenter.x = a / 2.;
    cirCenter.y = b / 2.;
    cirRadius = 0.5 * sqrtf(a * a + b * b - 4 * c);
}

你可能感兴趣的:(法兰盘螺纹定位与分类,opencv,算法)