要求

如图所示,需要找到白色过道中间直线方程,为了下一步放置挡板用,挡板中轴线与过道中轴线的误差不超过1cm。

效果图

找两条平行直线的中间直线_编程

程序代码
****************************************************************************************/
#include<opencv2/opencv.hpp>
#include <opencv.hpp>  
#include <iostream>  
#include<time.h>
#include<math.h>
#include<thread>

//#include "UART.h"
#include "findline.h"
//#include "Number.h"
//#include "Process.h"
//#include "Locate.h"
//#include "Manipulator_positioning.h"

using namespace cv;
using namespace std;

#define PI 3.1415926
RNG g_rng(12345);


//RGB转HSV
void RGB2HSV(double red, double green, double blue, double& hue, double& saturation, double& intensity)
{

    double r, g, b;
    double h, s, i;

    double sum;
    double minRGB, maxRGB;
    double theta;

    r = red / 255.0;
    g = green / 255.0;
    b = blue / 255.0;

    minRGB = ((r<g) ? (r) : (g));
    minRGB = (minRGB<b) ? (minRGB) : (b);

    maxRGB = ((r>g) ? (r) : (g));
    maxRGB = (maxRGB>b) ? (maxRGB) : (b);

    sum = r + g + b;
    i = sum / 3.0;

    if (i<0.001 || maxRGB - minRGB<0.001)
    {
        h = 0.0;
        s = 0.0;
    }
    else
    {
        s = 1.0 - 3.0*minRGB / sum;
        theta = sqrt((r - g)*(r - g) + (r - b)*(g - b));
        theta = acos((r - g + r - b)*0.5 / theta);
        if (b <= g)
            h = theta;
        else
            h = 2 * PI - theta;
        if (s <= 0.01)
            h = 0;
    }

    hue = (int)(h * 180 / PI);
    saturation = (int)(s * 100);
    intensity = (int)(i * 100);
}
//
///***** 求两点间距离*****/
//float getDistance(CvPoint pointO, CvPoint pointA)
//{
//  float distance;
//  distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2);
//  distance = sqrtf(distance);
//  return distance;
//}

//原点到直线的距离
//输入一堆直线,返回直线到坐标原点的距离
vector <float> get_0_distance(vector<Vec4i> lines)
{
    vector <float> diatance;

    for (unsigned int i = 0; i < lines.size(); i++)
    {
        double k = (double)(lines[i][1] - lines[i][3]) / (double)(lines[i][0] - lines[i][2]);//斜率
        double b = (double)lines[i][1] - (double)(lines[i][0])* k; //截距
        double diatance_temp = fabs(b / sqrt(1 + k*k));
        diatance.push_back(diatance_temp);
    }
    return diatance;

}


//输入一堆直线,返回每条直线与水平直线的角度,为弧度
vector <float> get_lines_arctan(vector<Vec4i> lines)
{
    float k = 0; //直线斜率
    vector <float> lines_arctan;//直线斜率的反正切值
    for (unsigned int i = 0; i<lines.size(); i++)
    {

        k = (double)(lines[i][3] - lines[i][1]) / (double)(lines[i][2] - lines[i][0]); //求出直线的斜率
        lines_arctan.push_back(atan(k));
    }
    return lines_arctan;
}

//输入一堆直线,返回每条直线的斜率和截距
//Vec2f为2个点的float,参照存储直线的数据结构
vector <Point2f> get_lines_fangcheng(vector<Vec4i> lines)
{
    float k = 0; //直线斜率
    float b = 0; //直线截距
    vector <Point2f> lines_fangcheng;//

    for (unsigned int i = 0; i<lines.size(); i++)
    {

        k = (double)(lines[i][3] - lines[i][1]) / (double)(lines[i][2] - lines[i][0]); //求出直线的斜率// -3.1415926/2-----+3.1415926/2
        b = (double)lines[i][1] - k * (double)lines[i][0]; //求出直线的截距
        lines_fangcheng.push_back(Point2f(k, b));
    }

    return lines_fangcheng;
}

int main()
{
    //载入原图,并找到红色挡板
    Mat srcImage = imread("7.jpg", 1);

    Mat srcImg;
    resize(srcImage, srcImg, Size(800, 600));//重定义图片大小

    namedWindow("原图", 0);
    imshow("原图", srcImg);
    waitKey(1);

    int width = srcImg.cols;
    int height = srcImg.rows;

    int x, y;
    double B = 0.0, G = 0.0, R = 0.0, H = 0.0, S = 0.0, V = 0.0;
    Mat vec_rgb = Mat::zeros(srcImg.size(), CV_8UC1);
    for (x = 0; x < height; x++)
    {
        for (y = 0; y < width; y++)
        {
            B = srcImg.at<Vec3b>(x, y)[0];
            G = srcImg.at<Vec3b>(x, y)[1];
            R = srcImg.at<Vec3b>(x, y)[2];
            RGB2HSV(R, G, B, H, S, V);
            //红色范围,范围参考的网上。可以自己调
            if ((H >= 312 && H <= 360 || H >= 0 && H <= 20) && (S >= 17 && S <= 100) && (V>18 && V < 100))
                vec_rgb.at<uchar>(x, y) = 255;
        }
    }

    namedWindow("hsv空间图像", 0);
    imshow("hsv空间图像", vec_rgb);
    waitKey(1);

    Mat element = getStructuringElement(MORPH_ELLIPSE, Size(2 * 1 + 1, 2 * 1 + 1), Point(1, 1));
    Mat element1 = getStructuringElement(MORPH_ELLIPSE, Size(2 * 3 + 1, 2 * 3 + 1), Point(3, 3));

    dilate(vec_rgb, vec_rgb, element1);//膨胀
    namedWindow("膨胀", 0);
    imshow("膨胀", vec_rgb);
    waitKey(1);

    vector<vector<Point>>contours,max_contours; //轮廓
    vector<Vec4i> hierarchy;//分层
    Mat drawing_text = Mat::zeros(vec_rgb.size(), CV_8UC3);
    findContours(vec_rgb, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);//找轮廓

    vector<vector<Point>> hull(contours.size());//用于存放凸包
    vector<float> length(contours.size());
    vector<float> Area_contours(contours.size()), Area_hull(contours.size()), Rectangularity(contours.size()), circularity(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {//历遍所有的轮廓

        length[i] = arcLength(contours[i], true);

        cout << "轮廓长度为" << length[i] << endl;

        if (length[i] >285 && length[i] <2300)
        {//通过长度匹配滤除小轮廓
            convexHull(Mat(contours[i]), hull[i], false);//把凸包找出来
            max_contours.push_back(hull[i]);//把提取出来的方框导入到新的轮廓组
        }
    }


    /*************************************************************************
    2、切割出红色挡板附近的区域
    *************************************************************************/
    //多边形逼近轮廓 + 获取矩形和圆形边界框
        vector<vector<Point> > contours_poly(max_contours.size());
        vector<Rect> boundRect(max_contours.size());
        vector<Point2f>center(max_contours.size());
        vector<float>radius(max_contours.size());

    //一个循环,遍历所有部分,进行本程序最核心的操作
        for (unsigned int i = 0; i < max_contours.size(); i++)
    {
            approxPolyDP(Mat(max_contours[i]), contours_poly[i], 3, true);//用指定精度逼近多边形曲线 
        boundRect[i] = boundingRect(Mat(contours_poly[i]));//计算点集的最外面(up-right)矩形边界
        minEnclosingCircle(contours_poly[i], center[i], radius[i]);//对给定的 2D点集,寻找最小面积的包围圆形 
    }



    Mat image_cut;      //从img中按照rect进行切割,此时修改image_cut时image中对应部分也会修改,因此需要copy  
    Mat image_copy_C3= Mat::zeros(800, 600, CV_8UC3);   //clone函数创建新的图片 彩色
    Mat image_copy_C1 = Mat::zeros(800, 600, CV_8UC1);   //clone函数创建新的图片 黑白

    int width1 = abs(boundRect[0].tl().x - boundRect[0].br().x);
    int height1 = abs(boundRect[0].tl().y - boundRect[0].br().y);

        Rect rect(boundRect[0].tl().x-80, boundRect[0].tl().y-80, width1+180, height1+180);   //创建一个Rect框,属于cv中的类,四个参数代表x,y,width,height  

        image_cut = Mat(srcImg, rect);      //从img中按照rect进行切割,此时修改image_cut时image中对应部分也会修改,因此需要copy  
        image_copy_C3 = image_cut.clone();   //clone函数创建新的图片 

        image_cut = Mat(vec_rgb, rect);      //从img中按照rect进行切割,此时修改image_cut时image中对应部分也会修改,因此需要copy  
        image_copy_C1 = image_cut.clone();   //clone函数创建新的图片  

        namedWindow("裁剪后彩色", 0);
        imshow("裁剪后彩色", image_copy_C3);
        waitKey(1);
        namedWindow("裁剪后黑白", 0);
        imshow("裁剪后黑白", image_copy_C1);
        waitKey(1);


        vector<vector<Point>>contours_1, RectContours; //轮廓
        Mat drawing_1 = Mat::zeros(image_copy_C1.size(), CV_8UC3);
        findContours(image_copy_C1, contours_1, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);//找轮廓

        if (contours_1.size()>0)//如果没有找到轮廓退出
    {
            vector<vector<Point>> hull(contours_1.size());//用于存放凸包
        int i = 0;
        vector<float> length(contours_1.size());
        vector<float> Area_contours(contours_1.size()), Area_hull(contours_1.size()), Rectangularity(contours_1.size()), circularity(contours_1.size());
        for (i = 0; i < contours_1.size(); i++)
        {//历遍所有的轮廓

            length[i] = arcLength(contours_1[i], true);

            if (length[i] >100 && length[i] <3500)
            {//通过长度匹配滤除小轮廓
                convexHull(Mat(contours_1[i]), hull[i], false);//把凸包找出来

                RectContours.push_back(hull[i]);//把提取出来的方框导入到新的轮廓组

            }

        }


        for (int i = 0; i < RectContours.size(); i++)
        {
            Scalar color = (0, 0, 255);//蓝色线画轮廓
            drawContours(drawing_1, RectContours, i, color, 1, 8, hierarchy, 0, Point());//根据轮廓点集contours_poly和轮廓结构hierarchy画出轮廓
            //画圆形
        }
    }


        namedWindow("红色挡板凸包", 0);
        imshow("红色挡板凸包", drawing_1);
        waitKey(1);



        /***** 4、ConnorPoint_Output 把所有排好序的角点输出,0号是左上角,顺时针输出(综合1、2、3)*****/
        vector<Index_Point> ConnorPoint(4);
        vector<Point> ConnorPoint_ordered(4);
        vector<vector<Point>> ConnorPoint_Output(4);

        for (int i = 0; i < RectContours.size(); i++)
        {
            ConnorPoint = FindConnor(RectContours[i]);
            ConnorPoint_ordered = FindFirstPoint(ConnorPoint);
            ConnorPoint_Output[0].push_back(ConnorPoint_ordered[2]);
            ConnorPoint_Output[1].push_back(ConnorPoint_ordered[1]);
            ConnorPoint_Output[2].push_back(ConnorPoint_ordered[0]);
            ConnorPoint_Output[3].push_back(ConnorPoint_ordered[3]);
        }

        Point A_top, B_bottom;  //红色挡板顶部中点,红色挡板底部中点

        A_top.x = ConnorPoint_Output[0][0].x + ConnorPoint_Output[1][0].x;
        A_top.y = ConnorPoint_Output[0][0].y + ConnorPoint_Output[1][0].y;

        B_bottom.x = ConnorPoint_Output[3][0].x + ConnorPoint_Output[2][0].x;
        B_bottom.y = ConnorPoint_Output[3][0].y + ConnorPoint_Output[2][0].y;



//再次通过找轮廓,然后拟合出直线,通过斜率滤除剩下四条数线(或横线),然后通过长度滤除挡板的边缘直线,剩下白色过道的直线方程,一切问题得到解决。
        Mat midImage, dstImage;//临时变量和目标图的定义
        cvtColor(image_copy_C3, image_copy_C3, CV_BGR2GRAY);//灰度化
        //【3】srcImage取大于阈值119的那部分
        image_copy_C3 = image_copy_C3 > 170;

        namedWindow("取阈值后的图", 0);
        imshow("取阈值后的图", image_copy_C3);

        blur(image_copy_C3, image_copy_C3, Size(3, 3));//进行模糊


        /*******  检测直线优化 开始 ****************************************************************/
        int cannyThreshold = 80;
        float factor = 2.5;


        vector<Vec4i> lines,lines_final;//定义一个矢量结构lines用于存放得到的线段矢量集合
        //HoughLinesP(midImage, lines, 1, CV_PI / 180, 320, 240, 30);
        Canny(image_copy_C3, midImage, cannyThreshold, cannyThreshold * factor);
        HoughLinesP(midImage, lines, 1, CV_PI / 180, 60, 50, 350);
        //最多的直线
        while (lines.size() >= 10)
        {
            cannyThreshold += 2;
            Canny(image_copy_C3, midImage, cannyThreshold, cannyThreshold * factor);


            HoughLinesP(midImage, lines, 1, CV_PI / 180, 60, 50, 350);
        }
        //最少的直线
        while (lines.size() <=3)
        {
            cannyThreshold -= 2;
            Canny(image_copy_C3, midImage, cannyThreshold, cannyThreshold * factor);
            HoughLinesP(midImage, lines, 1, CV_PI / 180, 60, 50, 350);
        }

        cout << "canny边缘检测阈值为:" << cannyThreshold << endl;

        Canny(image_copy_C3, midImage, cannyThreshold, cannyThreshold * factor);
        HoughLinesP(midImage, lines, 1, CV_PI / 180, 60, 50, 350);

        /*******  检测直线优化 结束 ****************************************************************/


        cvtColor(midImage, dstImage, COLOR_GRAY2BGR);//转化边缘检测后的图为灰度图


        printf("\n\n\t\t\t   当前使用的OpenCV版本为" CV_VERSION);
        cout << "\n共检测到原始  直线" << lines.size() << "条" << endl;



        //【4】依次计算出直线长度
        float  zhixian_changdu;
        for (size_t i = 0; i < lines.size(); i++)
        {
            Vec4i l = lines[i];
            zhixian_changdu = getDistance(Point(l[0], l[1]), Point(l[2], l[3]));
            cout << "\n直线长度为" << zhixian_changdu << endl;


            //通过直线长度滤除,只剩下两条最长的
            if (zhixian_changdu>320)
            {
                lines_final.push_back(lines[i]);


            }

        }

        //最终只能检测出两条直线
        if (lines_final.size() == 2)
        {
            //【4】依次在图中绘制出每条线段
            for (size_t i = 0; i < lines_final.size(); i++)
            {
                Vec4i l = lines_final[i];
                line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 1, LINE_AA);
            }

            vector <Point2f>  fangcheng_k_b;

            //输入一堆直线,返回每条直线的斜率和截距
            fangcheng_k_b = get_lines_fangcheng(lines_final);

            //最终推算出的白色过道中轴线,为距离和
            float  final_fangcheng_k, final_fangcheng_b;
            final_fangcheng_k = (fangcheng_k_b[0].x + fangcheng_k_b[1].x) / 2.0;//斜率K原理是一样的,但是检测出的平行线不可能理想的平行,这里取平均值
            final_fangcheng_b = (fangcheng_k_b[0].y + fangcheng_k_b[1].y) / 2.0;//截距就是二者的平均,取中间值

            //在图中绘制最终的那一条直线
            Point pt1, pt2;

            pt1.x = -(int)(final_fangcheng_b / final_fangcheng_k);
            pt1.y = 0;
            pt2.x = 0;
            pt2.y = (int)final_fangcheng_b;
            line(dstImage, pt1, pt2, Scalar(255, 0, 0), 1, LINE_AA);
            //line(dstImage, Point(0,0), Point(100,100), Scalar(255, 0, 0), 1, LINE_AA);

        }



        //【6】边缘检测后的图 
        namedWindow("【边缘检测后的图】", 0);//参数为零,则可以自由拖动
        imshow("【边缘检测后的图】", midImage);

        namedWindow("【检测直线效果图】", 0);//参数为零,则可以自由拖动
        imshow("【检测直线效果图】", dstImage);

        waitKey(0);





}