轮廓提取

轮廓概念

一般用二值化的图像 对轮廓1的描述 [2,-1,-1,0]

轮廓检测函数

hierarchy描述的轮廓的结构关系

轮廓绘制函数

示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    Mat img = imread("keys.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    imshow("原图", img);
    Mat gray, binary;
    cvtColor(img, gray, COLOR_BGR2GRAY);  //转化成灰度图
    GaussianBlur(gray, gray, Size(13, 13), 4, 4);  //平滑滤波
    threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自适应二值化

                                                                     // 轮廓发现与绘制
    vector<vector<Point>> contours;  //轮廓
    vector<Vec4i> hierarchy;  //存放轮廓结构变量
    findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    //绘制轮廓
    for (int t = 0; t < contours.size(); t++)
    {
        drawContours(img, contours, t, Scalar(0, 0, 255), 2, 8);
    }
    //输出轮廓结构描述子
    for (int i = 0; i < hierarchy.size(); i++)
    {
        cout << hierarchy[i] << endl;
    }

    //显示结果
    imshow("轮廓检测结果", img);
    waitKey(0);
    return 0;
}

轮廓信息统计

轮廓面积

方向性指的是轮廓的像素点是否具有方向性,可以通过正负得出像素点是正序还是负序给出的 示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    //用四个点表示三角形轮廓
    vector<Point> contour;
    contour.push_back(Point2f(0, 0));
    contour.push_back(Point2f(10, 0));
    contour.push_back(Point2f(10, 10));
    contour.push_back(Point2f(5, 5));
    double area = contourArea(contour);
    cout << "area =" << area << endl;

    Mat img = imread("coins.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    imshow("原图", img);
    Mat gray, binary;
    cvtColor(img, gray, COLOR_BGR2GRAY);  //转化成灰度图
    GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑滤波
    threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自适应二值化

                                                                     // 轮廓检测
    vector<vector<Point>> contours;  //轮廓
    vector<Vec4i> hierarchy;  //存放轮廓结构变量
    findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

    //输出轮廓面积
    for (int t = 0; t < contours.size(); t++)
    {
        double area1 = contourArea(contours[t]);
        cout << "第" << t << "轮廓面积=" << area1 << endl;
    }
    return 0;
}
轮廓长度提取

示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    //用四个点表示三角形轮廓
    vector<Point> contour;
    contour.push_back(Point2f(0, 0));
    contour.push_back(Point2f(10, 0));
    contour.push_back(Point2f(10, 10));
    contour.push_back(Point2f(5, 5));

    double length0 = arcLength(contour, true);
    double length1 = arcLength(contour, false);
    cout << "length0 =" << length0 << endl;
    cout << "length1 =" << length1 << endl;

    Mat img = imread("coins.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    imshow("原图", img);
    Mat gray, binary;
    cvtColor(img, gray, COLOR_BGR2GRAY);  //转化成灰度图
    GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑滤波
    threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU);  //自适应二值化

    // 轮廓检测
    vector<vector<Point>> contours;  //轮廓
    vector<Vec4i> hierarchy;  //存放轮廓结构变量
    findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());

    //输出轮廓长度
    for (int t = 0; t < contours.size(); t++)
    {
        double length2 = arcLength(contours[t], true);
        cout << "第" << t << "个轮廓长度=" << length2 << endl;
    }
    return 0;
}

轮廓的拟合

boudingRect给出的是轮廓的外接矩形的Rect,需要输入灰度图像或者2D点集 minAreaRect得到最小的外接矩形(允许矩形进行旋转)得到的旋转的四个点的坐标和中心点的坐标,可以用minarearect.center()minarearect.points()获取 approxPloyDP获得的是多边形顶点的坐标 示例

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("stuff.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat img1, img2;
    img.copyTo(img1);  //深拷贝用来绘制最大外接矩形
    img.copyTo(img2);  //深拷贝用来绘制最小外接矩形
    imshow("img", img);

    // 去噪声与二值化
    Mat canny;
    Canny(img, canny, 80, 160, 3, false);
    imshow("", canny);

    //膨胀运算,将细小缝隙填补上
    Mat kernel = getStructuringElement(0, Size(3, 3));
    dilate(canny, canny, kernel);

    // 轮廓发现与绘制
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(canny, contours, hierarchy, 0, 2, Point());

    //寻找轮廓的外接矩形
    for (int n = 0; n < contours.size(); n++)
    {
        // 最大外接矩形
        Rect rect = boundingRect(contours[n]);
        rectangle(img1, rect, Scalar(0, 0, 255), 2, 8, 0);

        // 最小外接矩形
        RotatedRect rrect = minAreaRect(contours[n]);
        Point2f points[4];
        rrect.points(points);  //读取最小外接矩形的四个顶点
        Point2f cpt = rrect.center;  //最小外接矩形的中心

        // 绘制旋转矩形与中心位置
        for (int i = 0; i < 4; i++)
        {
            if (i == 3)
            {
                line(img2, points[i], points[0], Scalar(0, 255, 0), 2, 8, 0);
                break;
            }
            line(img2, points[i], points[i + 1], Scalar(0, 255, 0), 2, 8, 0);
        }
        //绘制矩形的中心
        circle(img2, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
    }

    //输出绘制外接矩形的结果
    imshow("max", img1);
    imshow("min", img2);
    waitKey(0);
    return 0;
}

示例(多边形拟合)

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

//绘制轮廓函数
void drawapp(Mat result, Mat img2)
{
    for (int i = 0; i < result.rows; i++)
    {
        //最后一个坐标点与第一个坐标点连接
        if (i == result.rows - 1)
        {
            Vec2i point1 = result.at<Vec2i>(i);
            Vec2i point2 = result.at<Vec2i>(0);
            line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
            break;
        }
        Vec2i point1 = result.at<Vec2i>(i);
        Vec2i point2 = result.at<Vec2i>(i + 1);
        line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
    }
}

int main(int argc, const char* argv[])
{
    Mat img = imread("approx.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    // 边缘检测
    Mat canny;
    Canny(img, canny, 80, 160, 3, false);
    //膨胀运算
    Mat kernel = getStructuringElement(0, Size(3, 3));
    dilate(canny, canny, kernel);

    // 轮廓发现与绘制
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(canny, contours, hierarchy, 0, 2, Point());

    //绘制多边形
    for (int t = 0; t < contours.size(); t++)
    {
        //用最小外接矩形求取轮廓中心
        RotatedRect rrect = minAreaRect(contours[t]);
        Point2f center = rrect.center;
        circle(img, center, 2, Scalar(0, 255, 0), 2, 8, 0);

        Mat result;
        approxPolyDP(contours[t], result, 4, true);  //多边形拟合
        drawapp(result, img);
        cout << "corners : " << result.rows << endl;

        //判断形状和绘制轮廓
        if (result.rows == 3)
        {
            putText(img, "triangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
        }
        if (result.rows == 4)
        {
            putText(img, "rectangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
        }
        if (result.rows == 8)
        {
            putText(img, "poly-8", center, 0, 1, Scalar(0, 255, 0), 1, 8);
        }
        if (result.rows > 12)
        {
            putText(img, "circle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
        }
    }
    imshow("result", img);
    waitKey(0);
    return 0;
}

凸包检测

把轮廓拟合成一个凸多边形 可以直接的找到手所在区域的大小 示例

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("hand.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    // 二值化,凸包检测只能输入2D点
    Mat gray, binary;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    threshold(gray, binary, 105, 255, THRESH_BINARY);

    //开运算消除细小区域
    Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(binary, binary, MORPH_OPEN, k);
    imshow("binary", binary);

    // 轮廓发现
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(binary, contours, hierarchy, 0, 2, Point());
    for (int n = 0; n < contours.size(); n++)
    {
        //计算凸包
        vector<Point> hull;
        convexHull(contours[n], hull);
        //绘制凸包
        for (int i = 0; i < hull.size(); i++)
        {
            //绘制凸包顶点
            circle(img, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);
            //连接凸包
            if (i == hull.size() - 1)
            {
                line(img, hull[i], hull[0], Scalar(0, 0, 255), 2, 8, 0);
                break;
            }
            line(img, hull[i], hull[i + 1], Scalar(0, 0, 255), 2, 8, 0);
        }
    }
    imshow("hull", img);
    waitKey(0);
    return 0;
}

直线检测

检测直线的霍夫变换原理

原空间的点是参数空间的线,原空间的线是参数空间的点 参数空间交于一点的多条线,表示原空间并线的多个点 为了防止b=0,导致在斜率不存在,用(r,θ)的方式来表示直线(直线距离原点距离以及角度)

检测直线函数

检测结果是(r,θ)需要计算得到两个点才能绘制 示例

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawLine(Mat& img, //要标记直线的图像
    vector<Vec2f> lines,   //检测的直线数据
    double rows,   //原图像的行数(高)
    double cols,  //原图像的列数(宽)
    Scalar scalar,  //绘制直线的颜色
    int n  //绘制直线的线宽
)
{
    Point pt1, pt2;
    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0];  //直线距离坐标原点的距离
        float theta = lines[i][1];  //直线过坐标原点垂线与x轴夹角
        double a = cos(theta);  //夹角的余弦值
        double b = sin(theta);  //夹角的正弦值
        double x0 = a * rho, y0 = b * rho;  //直线与过坐标原点的垂线的交点
        double length = max(rows, cols);  //图像高宽的最大值
        //计算直线上的一点
        pt1.x = cvRound(x0 + length * (-b));//cvround可以把浮点数近似为整数
        pt1.y = cvRound(y0 + length * (a));//简单几何学,不懂可以画个图
        //计算直线上另一点
        pt2.x = cvRound(x0 - length * (-b));
        pt2.y = cvRound(y0 - length * (a));
        //两点绘制一条直线
        line(img, pt1, pt2, scalar, n);//选取大数保证坐标在图像外
    }
}

int main()
{
    Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat edge;

    //检测边缘图像,并二值化
    Canny(img, edge, 80, 180, 3, false);
    threshold(edge, edge, 170, 255, THRESH_BINARY);

    //用不同的累加器进行检测直线
    vector<Vec2f> lines1, lines2;
    HoughLines(edge, lines1, 1, CV_PI / 180, 50, 0, 0);//CV_PI为1度
    HoughLines(edge, lines2, 1, CV_PI / 180, 150, 0, 0);

    //在原图像中绘制直线
    Mat img1, img2;
    img.copyTo(img1);
    img.copyTo(img2);
    drawLine(img1, lines1, edge.rows, edge.cols, Scalar(255), 2);
    drawLine(img2, lines2, edge.rows, edge.cols, Scalar(255), 2);

    //显示图像
    imshow("edge", edge);
    imshow("img", img);
    imshow("img1", img1);
    imshow("img2", img2);
    waitKey(0);
    return 0;
}
渐进霍夫变换

函数输出的线段的端点坐标 image一般输入的是用Canny算法得到的图像边缘 示例

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat edge;

    //检测边缘图像,并二值化
    Canny(img, edge, 80, 180, 3, false);
    //threshold(edge, edge, 170, 255, THRESH_BINARY);//Canny算法得到的图像已经是二值化图像了

    //利用渐进概率式霍夫变换提取直线
    vector<Vec4i> linesP1, linesP2;
    HoughLinesP(edge, linesP1, 1, CV_PI / 180, 150, 30, 10);  //两个点连接最大距离10
    HoughLinesP(edge, linesP2, 1, CV_PI / 180, 150, 30, 30);  //两个点连接最大距离30

    //绘制两个点连接最大距离10直线检测结果
    Mat img1;
    img.copyTo(img1);
    for (size_t i = 0; i < linesP1.size(); i++)
    {
        line(img1, Point(linesP1[i][0], linesP1[i][1]),
            Point(linesP1[i][2], linesP1[i][3]), Scalar(255), 3);
    }

    //绘制两个点连接最大距离30直线检测结果
    Mat img2;
    img.copyTo(img2);
    for (size_t i = 0; i < linesP2.size(); i++)
    {
        line(img2, Point(linesP2[i][0], linesP2[i][1]),
            Point(linesP2[i][2], linesP2[i][3]), Scalar(255), 3);
    }

    //显示图像
    imshow("img1", img1);
    imshow("img2", img2);
    waitKey(0);
    return 0;
}

圆形检测

霍夫变换同样可以检测图像中是否存在圆形,检测方法与检测直线相似,都是将图像空间x-y直角坐标系中的像素投影到参数空间中,之后寻找是否存在交点,在圆形参数空间中,数学描述的形式如下

霍夫圆检测

函数会自动调用canny检测器,因此不需要二值化,输出的结果在Vec3f里面,其中前两个参数是圆形的中心坐标,第三个参数是圆形的半径,该函数的第三个参数是检测圆形的方法标志,目前仅支持HOUGH_GRADIENT方法,dp = 1时离散化后分辨率图像相同,等于2的时候离散化图像高度和宽度只有一般,mindist的最小距离,防止错误的检测到多个相邻源泉,param1为Canny阈值的较大值,较小值默认为一半。 示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("test1.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    resize(img, img, Size(3840 / 5, 2160 / 5));
    imshow("原图", img);
    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    equalizeHist(gray, gray);
    GaussianBlur(gray, gray, Size(9, 9), 2, 2);  //平滑滤波

    //检测圆形
    vector<Vec3f> circles;
    double dp = 2; //
    double minDist = 80;  //两个圆心之间的最小距离
    double	param1 = 180;  //Canny边缘检测的较大阈值
    double	param2 = 100;  //累加器阈值
    int min_radius = 40;  //圆形半径的最小值
    int max_radius = 100;  //圆形半径的最大值
    HoughCircles(gray, circles, HOUGH_GRADIENT, dp, minDist, param1, param2,
        min_radius, max_radius);

    //图像中标记出圆形
    for (size_t i = 0; i < circles.size(); i++)
    {
        //读取圆心
        Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        //读取半径
        int radius = cvRound(circles[i][2]);
        //绘制圆心
        circle(img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
        //绘制圆
        circle(img, center, radius, Scalar(0, 0, 255), 3, 8, 0);
    }

    //显示结果
    imshow("圆检测结果", img);
    waitKey(0);
    return 0;
}

结果(参数可以拿去用)(别忘了均衡化)

点集的拟合

点集拟合的含义

直线拟合

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    Vec4f lines;  //存放你和后的直线
    vector<Point2f> point;  //待检测是否存在直线的所有点
    const static float Points[20][2] = {
        { 0.0f,   0.0f },{ 10.0f,  11.0f },{ 21.0f,  20.0f },{ 30.0f,  30.0f },
        { 40.0f,  42.0f },{ 50.0f,  50.0f },{ 60.0f,  60.0f },{ 70.0f,  70.0f },
        { 80.0f,  80.0f },{ 90.0f,  92.0f },{ 100.0f, 100.0f },{ 110.0f, 110.0f },
        { 120.0f, 120.0f },{ 136.0f, 130.0f },{ 138.0f, 140.0f },{ 150.0f, 150.0f },
        { 160.0f, 163.0f },{ 175.0f, 170.0f },{ 181.0f, 180.0f },{ 200.0f, 190.0f }
    };
    //将所有点存放在vector中,用于输入函数中
    for (int i = 0; i < 20; i++)
    {
        point.push_back(Point2f(Points[i][0], Points[i][1]));
    }
    //参数设置
    double param = 0;  //距离模型中的数值参数C
    double reps = 0.01;  //坐标原点与直线之间的距离精度
    double aeps = 0.01;  //角度精度
    fitLine(point, lines, DIST_L1, 0, 0.01, 0.01);
    double k = lines[1] / lines[0];  //直线斜率
    cout << "直线斜率:" << k << endl;
    cout << "直线上一点坐标x:" << lines[2] << ", y::" << lines[3] << endl;
    cout << "直线解析式:y=" << k << "(x-" << lines[2] << ")+" << lines[3] << endl;
    return 0;
}

三角形和圆形拟合 示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    Mat img(500, 500, CV_8UC3, Scalar::all(0));
    RNG& rng = theRNG();  //生成随机点

    while (true)
    {
        int i, count = rng.uniform(1, 101);
        vector<Point> points;
        //生成随机点
        for (i = 0; i < count; i++)
        {
            Point pt;
            pt.x = rng.uniform(img.cols / 4, img.cols * 3 / 4);
            pt.y = rng.uniform(img.rows / 4, img.rows * 3 / 4);
            points.push_back(pt);
        }

        //寻找包围点集的三角形 
        vector<Point2f> triangle;
        double area = minEnclosingTriangle(points, triangle);

        //寻找包围点集的圆形
        Point2f center;
        float radius = 0;
        minEnclosingCircle(points, center, radius);

        //创建两个图片用于输出结果
        img = Scalar::all(0);
        Mat img2;
        img.copyTo(img2);

        //在图像中绘制坐标点
        for (i = 0; i < count; i++)
        {
            circle(img, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
            circle(img2, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
        }
            
        //绘制三角形
        for (i = 0; i < 3; i++)
        {
            if (i==2)
            {
                line(img, triangle[i], triangle[0], Scalar(255, 255, 255), 1, 16);
                break;
            }
            line(img, triangle[i], triangle[i + 1], Scalar(255, 255, 255), 1, 16);
        }

        //绘制圆形
        circle(img2, center, cvRound(radius), Scalar(255, 255, 255), 1, LINE_AA);

        //输出结果
        imshow("triangle", img);
        imshow("circle", img2);

        //按q键或者ESC键退出程序
        char key = (char)waitKey();
        if (key == 27 || key == 'q' || key == 'Q')
        {
            break;
        }
    }
    return 0;
}

测你们码(QR码)

QR二维码的识别原理

无论二维码尺寸如何,位置探测图形宽度必须是1:1:3:1的形式(1黑1白3黑1白) 可以通过对其标志把倾斜的二维码透视变换回去

二维码定位函数与识别函数

二维码的直接定位和识别函数

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("qrcode2.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat gray, qrcode_bin;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    QRCodeDetector qrcodedetector;
    vector<Point> points;
    string information;
    bool isQRcode;
    isQRcode = qrcodedetector.detect(gray, points);  //识别二维码
    if (isQRcode)
    {
        //解码二维码
        information = qrcodedetector.decode(gray, points, qrcode_bin);
        cout << points << endl;  //输出二维码四个顶点的坐标
    }
    else
    {
        cout << "无法识别二维码,请确认图像时候含有二维码" << endl;
        return -1;
    }
    //绘制二维码的边框
    for (int i = 0; i < points.size(); i++)
    {
        if (i == points.size() - 1)
        {
            line(img, points[i], points[0], Scalar(0, 0, 255), 2, 8);
            break;
        }
        line(img, points[i], points[i + 1], Scalar(0, 0, 255), 2, 8);
    }
    //将解码内容输出到图片上
    putText(img, information.c_str(), Point(20, 30), 0, 1.0, Scalar(0, 0, 255), 2, 8);

    //利用函数直接定位二维码并解码
    string information2;
    vector<Point> points2;
    information2 = qrcodedetector.detectAndDecode(gray, points2);
    cout << points2 << endl;
    putText(img, information2.c_str(), Point(20, 55), 0, 1.0, Scalar(0, 0, 0), 2, 8);

    //输出结果
    imshow("result", img);
    namedWindow("qrcode_bin", WINDOW_NORMAL);
    imshow("qrcode_bin", qrcode_bin);
    waitKey(0);
    return 0;
}