OpenCV 4.x API 详解与C++实例-结构分析和形状描述

第九节 结构分析和形状描述

OpenCV的imgproc模块对物体描述提供了丰富的API,比如弧长计算、轮廓查找、质心查找等等。

1、cv::findContours


查找二值图像中的轮廓。

void cv::findContours(InputArray image,OutputArrayOfArrays contours,OutputArray hierarchy,int mode,int method,Point offset = Point())

该函数使用算法(Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.)从二值图像中检索轮廓。 轮廓是用于形状分析以及对象检测和识别的有用工具。

参数如下:

参数名称 参数描述
image 输入图像。一个8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为binary。可以通过使用compare, inRange, threshold , adaptiveThreshold, Canny等方法来生成二值图像。如果mode等于RETR_CCOMP或RETR_FLOODFILL,则输入也可以是标签(CV_32SC1)的32位整数图像。
contours 检测到的轮廓。 每个轮廓都存储为点的向量(例如std :: vector <std :: vector >)。
hierarchy 可选的输出向量(例如std :: vector ),包含有关图像拓扑的信息。 它具有与轮廓数量一样多的元素。 对于每个第i个轮廓轮廓[i],将元素等级[i] [0],等级[i] [1],等级[i] [2]和等级[i] [3]设置为0- 在同一层次级别上,基于下一个和上一个轮廓的轮廓中的索引,分别是第一个子轮廓和父轮廓。 如果对于轮廓i,没有下一个,上一个,父级或嵌套的轮廓,则hierarchy [i]的相应元素将为负。
mode 轮廓检测模式,请参考 RetrievalModes
method 轮廓近似法, 请参考ContourApproximationModes
offset 每个轮廓点移动的可选偏移量。 如果从图像ROI中提取轮廓,然后在整个图像上下文中对其进行分析,这将非常有用。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE );
    cv::Scalar color(255,0,255);
    
    // 绘制轮廓
    cv::drawContours( dst, contours,-1, color);

    // 显示图像
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

其他的重载函数如下:

void cv::findContours(InputArray image,OutputArrayOfArrays contours,int mode,int method,Point offset = Point())

2、cv::approxPolyDP


以指定的精度逼近多边形曲线。

void cv::approxPolyDP(InputArray curve,OutputArray approxCurve,
double epsilon,bool closed)

函数cv :: approxPolyDP用一条具有较少顶点的曲线或多边形逼近一条曲线或多边形,以使它们之间的距离小于或等于指定的精度。 它使用Douglas-Peucker算法http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm

参数如下:

参数名称 参数描述
curve 存储在std :: vector中的2D点的输入向量或 Mat
approxCurve 近似结果。 类型应与输入曲线的类型匹配。
epsilon 指定近似精度的参数。 这是原始曲线与其近似值之间的最大距离。
closed 如果为true,则近似曲线是闭合的(其第一个和最后一个顶点已连接)。 否则,它不会关闭。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{

    //读取图片
    cv::Mat src = cv::imread("images/circle-2.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,200,255,cv::THRESH_BINARY_INV);

    // 查找轮廓
    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarcy;

    cv::findContours(imgThresh, contours, hierarcy, 0, cv::CHAIN_APPROX_NONE);
    //用于存放折线点集
    vector<vector<cv::Point>> contours_poly(contours.size());
     cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);
    // 拟合多边开曲线
    for (int i = 0; i< contours.size(); i++)
    {
        cv::approxPolyDP(cv::Mat(contours[i]), contours_poly[i], 15, true);

        cv::drawContours(dst, contours_poly, i, cv::Scalar(0, 255, 255), 2, 8);  //绘制
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

3、cv::arcLength、cv::contourArea


1)cv::arcLength:计算轮廓周长或曲线长度。

double cv::arcLength(InputArray curve,bool closed)

该函数计算曲线长度或闭合轮廓周长。

参数如下:

Parameters

参数名称 参数描述
curve 二维点的输入向量,存储在std :: vector 或Mat中。
closed 指示曲线是否闭合的标志。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <sstream>
using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,200,255,cv::THRESH_BINARY_INV);

    // 查找轮廓
    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarcy;
    cv::findContours(imgThresh, contours, hierarcy, 0, cv::CHAIN_APPROX_NONE);

    int font_face = cv::FONT_HERSHEY_COMPLEX;
    double font_scale = 0.5;

    // 绘制轮廓
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);
    cv::Mat dstInfo = dst.clone();
    cv::RNG rng(-1);
    // 计算轮廓周长
    for(int i = 0;i < contours.size();i++){
        double arclen = cv::arcLength(contours[i],true);
        int r = rng.uniform(128,255);
        int g = rng.uniform(128,255);
        int b = rng.uniform(128,255);

        cv::drawContours( dst, contours,i, cv::Scalar(b,g,r));
        stringstream ss;
        ss << "contours[" <<i << "]="<< arclen;
        cv::putText(dstInfo,ss.str(),cv::Point(50,(i + 1) * 20),font_face,font_scale,cv::Scalar(b,g,r));
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::imshow("arc info",dstInfo);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

2)cv::contourArea:计算轮廓面积。

double cv::contourArea(InputArray contour,bool oriented = false)

该函数计算轮廓面积。 与矩类似,使用格林公式计算面积。 因此,如果您使用drawContours或fillPoly绘制轮廓,则返回的区域和非零像素数可能会有所不同。 同样,对于具有自相交的轮廓,该函数肯定会给出错误的结果。

参数如下:

参数名称 参数描述
contour 二维点(轮廓顶点)的输入向量,存储在std :: vector或Mat中
oriented 定向区域标志。 如果为true,则函数将根据轮廓方向(顺时针或逆时针)返回带符号的区域值。 使用此功能,您可以通过获取区域的符号来确定轮廓的方向。 默认情况下,该参数为false,表示返回绝对值。
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, 4));
double area0 = contourArea(contour);
vector<Point> approx;
approxPolyDP(contour, approx, 5, true);
double area1 = contourArea(approx);
cout << "area0 =" << area0 << endl <<
        "area1 =" << area1 << endl <<
        "approx poly vertices" << approx.size() << endl;

4、cv::boundingRect


计算点集或灰度图像的非零像素的右边界矩形。

Rect cv::boundingRect(InputArray array)

该函数为指定的点集或灰度图像的非零像素计算并返回最小的垂直边界矩形。

参数 array为输入存储在std :: vector或Mat中的灰度图像或2D点集。

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

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,200,255,cv::THRESH_BINARY_INV);

    // 查找轮廓
    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarcy;
    cv::findContours(imgThresh, contours, hierarcy, 0, cv::CHAIN_APPROX_NONE);

    // 绘制轮廓边框
    cv::Mat dst;
    src.copyTo(dst);
    for(int i = 0;i < contours.size();i++){
        // 计算轮廓边框
        cv::Rect rect = cv::boundingRect(contours[i]);
        
        cv::rectangle(dst,rect,cv::Scalar(0,0,255),3);
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

5、cv::minAreaRect、cv::boxPoints


1)cv::minAreaRect:查找包含输入2D点集的最小面积的旋转矩形。

RotatedRect cv::minAreaRect(InputArray points)

该函数计算并返回指定点集的最小区域边界矩形(可能已旋转)。 请注意,当数据接近包含Mat元素的边界时,返回的RotatedRect可以包含负索引。

参数points为二维点的输入向量,存储在std :: vector <>或Mat中。

2)cv::boxPoints:查找旋转的矩形的四个顶点。 绘制旋转的矩形很有用。

void cv::boxPoints(RotatedRect box,OutputArray points)

  • box:为旋转矩形
  • points:输出的旋转矩阵四个顶点

其调用结果与RotatedRect的points方法相同。

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

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,200,255,cv::THRESH_BINARY_INV);

    // 查找轮廓
    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarcy;
    cv::findContours(imgThresh, contours, hierarcy, 0, cv::CHAIN_APPROX_NONE);

    cv::Mat dst;
    src.copyTo(dst);

    for(int i = 0;i < contours.size();i++){
        // 计算最小面积矩形
        cv::RotatedRect rotatedRect = cv::minAreaRect(contours[i]);
        cv::Mat points;
        cv::Point2f vtx[4];
        cv::boxPoints(rotatedRect,points);//调用效果与rotatedRect.points(vtx);相同

        // 绘制旋转矩形
        for( int j = 0; j < 4; j++ ){
            cv::Point2f p1 = points.row(j).at<cv::Point2f>(0);
            cv::Point2f p2 = points.row((j+1)%4).at<cv::Point2f>(0);

            cv::line(dst, p1, p2, cv::Scalar(0, 0, 255), 3, cv::LINE_AA);
        }
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

6、cv::connectedComponents、cv::connectedComponentsWithStats

1)cv::connectedComponents:计算二值图像的连通组件,并标记出来。

int cv::connectedComponents(InputArray image,OutputArray labels,int connectivity,int ltype,int ccltype)

具有4或8路连通性的图像-返回N,即标签总数[0,N-1],其中0表示背景标签。 ltype指定输出标签图像类型,这是基于标签总数或源图像中像素总数的重要考虑因素。 ccltype指定要使用的连接的组件标记算法,当前支持Grana(BBDT)和Wu的(SAUF)[268]算法,有关详细信息,请参见ConnectedComponentsAlgorithmsTypes。 请注意,SAUF算法会强制标签的行主要排序,而BBDT则不会。 如果启用了至少一个允许的并行框架,并且图像的行数至少是getNumberOfCPUs返回数的两倍,则此函数使用Grana和Wu算法的并行版本。

参数如下:

参数名称 参数描述
image 要标记的8位单通道图像
labels 目标标记图像
connectivity 连通域数量,比如4连通域或8连通域,则为4或8
ltype 输出目标标记类型数据类型,目前仅支持 CV_32S 和CV_16U。
ccltype 连接组件算法类型 (请参考 ConnectedComponentsAlgorithmsTypes)。

目前OpenCV支持如下算法类型:

名称 描述
CCL_WU SAUF算法用于8路连接,SAUF算法用于4路连接。
CCL_DEFAULT BBDT算法用于8路连接,SAUF算法用于4路连接。
CCL_GRANA BBDT算法用于8路连接,SAUF算法用于4路连接。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,0,255,cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 计算连通组件
    cv::Mat labelsImage;
    int nLabels = cv::connectedComponents(imgThresh,labelsImage,8,CV_32S,cv::CCL_DEFAULT);
    // 不同区域用不同颜色表示
    vector<cv::Vec3b> colors(nLabels);

    //背景黑色
    colors[0] = cv::Vec3b(0, 0, 0);

    //对于由不同lable标记的联通区域,进行随机上色
    cv::RNG rng(-1);
    for (int label = 1; label < nLabels; label++)
    {
        colors[label] = cv::Vec3b((rng.uniform(0,255) & 255),
                                  (rng.uniform(0,255) & 255),
                                  (rng.uniform(0,255) & 255));
    }

    cv::Mat dst(src.size(),CV_8UC3);

    for (int r = 0; r < dst.rows; r++)
    {
        for (int c = 0; c < dst.cols; c++)
        {
            int label = labelsImage.at<int>(r, c);
            cv::Vec3b &pixel = dst.at<cv::Vec3b>(r, c);
            pixel = colors[label];
        }
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::imshow("threshold",imgThresh);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

其他重载函数如下:

  • int cv::connectedComponents(InputArray image,OutputArray labels,int connectivity = 8,int ltype = CV_32S)

2)cv::connectedComponentsWithStats:计算二值图像的连接的组件标签图像,并为每个标签产生统计输出。

int cv::connectedComponentsWithStats(InputArray image,OutputArray labels,OutputArray stats,OutputArray centroids,int connectivity,int ltype,int ccltype)

具有4或8路连通性的图像-返回N,即标签总数[0,N-1],其中0表示背景标签。 ltype指定输出标签图像类型,这是基于标签总数或源图像中像素总数的重要考虑因素。 ccltype指定要使用的连接组件标记算法,当前支持Grana(BBDT)和Wu(SAUF)[268]算法,有关详细信息,请参见ConnectedComponentsAlgorithmsTypes。 请注意,SAUF算法会强制标签的行主要排序,而BBDT则不会。 如果启用了至少一个允许的并行框架,并且图像的行数至少是getNumberOfCPUs返回数的两倍,则此函数使用Grana和Wu算法的并行版本(包括统计信息)。

参数名称 参数描述
image 要标记的8位单通道图像
labels 目标标记图像
stats 每个标签(包括背景标签)的统计信息输出。 通过stats(标签,COLUMN)访问统计信息,其中COLUMN是ConnectedComponentsTypes之一。 数据类型为CV_32S。
centroids 每个标签的质心输出,包括背景标签。 通过x的质心(label,0)和y的质心(label,1)访问质心。 数据类型为CV_64F。
connectivity 8路或4路连接分别为8或4
ltype 输出目标标记类型数据类型,目前仅支持 CV_32S 和CV_16U。
ccltype 连接组件算法类型 (请参考ConnectedComponentsAlgorithmsTypes)。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成灰度图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);

    // 阈值化处理
    cv::Mat imgThresh;
    cv::threshold(gray,imgThresh,0,255,cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 计算连通组件
    cv::Mat labelsImage;
    cv::Mat stats;
    cv::Mat centroids;
    int nLabels = cv::connectedComponentsWithStats(imgThresh, labelsImage, stats, centroids);
    // 不同区域用不同颜色表示
    vector<cv::Vec3b> colors(nLabels);

    //背景黑色
    colors[0] = cv::Vec3b(0, 0, 0);

    //对于由不同lable标记的联通区域,进行随机上色
    cv::RNG rng(-1);
    for (int label = 1; label < nLabels; label++)
    {
        colors[label] = cv::Vec3b((rng.uniform(0,255) & 255),
                                  (rng.uniform(0,255) & 255),
                                  (rng.uniform(0,255) & 255));
    }

    cv::Mat dst(src.size(),CV_8UC3);

    for (int r = 0; r < dst.rows; r++)
    {
        for (int c = 0; c < dst.cols; c++)
        {
            int label = labelsImage.at<int>(r, c);
            cv::Vec3b &pixel = dst.at<cv::Vec3b>(r, c);
            pixel = colors[label];
        }
    }

    for(int i=0; i<stats.rows; i++)
    {
        int x = stats.at<int>(cv::Point(0, i));
        int y = stats.at<int>(cv::Point(1, i));
        int w = stats.at<int>(cv::Point(2, i));
        int h = stats.at<int>(cv::Point(3, i));

        cv::Scalar color(0,0,255);
        cv::Rect rect(x,y,w,h);
        cv::rectangle(dst, rect, color,3);
    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::imshow("threshold",imgThresh);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

其他重载函数如下:

  • int cv::connectedComponentsWithStats(InputArray image,OutputArray labels,OutputArray stats,OutputArray centroids,int connectivity = 8,int ltype = CV_32S)

7、cv::convexHull


根据点集查找凸包。

void cv::convexHull(InputArray points,OutputArray hull,bool clockwise = false,bool returnPoints = true)

函数cv :: convexHull使用Sklansky算法查找2D点集的凸包,该算法在当前实现中具有O(NlogN)复杂度。

参数名称 参数描述
points 输入2D点集,储存在 std::vector 或 Mat中。
hull 输出凸包。它可以是索引的整数向量,也可以是点的向量。在第一种情况下,外壳元素是原始数组中凸包点的基于0的索引(因为凸包点集是原始点集的子集)。 在第二种情况下,外壳元素本身就是凸形外壳点。
clockwise 方向标志。 如果为true,则输出凸包为顺时针方向。 否则,其方向为逆时针方向。 假定坐标系的X轴指向右侧,而Y轴指向上方。
returnPoints 操作标志。 在矩阵的情况下,当标志为true时,函数将返回凸包点。 否则,它返回凸包点的索引。 当输出数组为std :: vector时,将忽略该标志,并且输出取决于向量的类型:std :: vector 表示returnPoints = false,std :: vector 表示returnPoints = true。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/plane.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE );
    cv::Scalar color(255,0,255);
    cv::drawContours( dst, contours,-1, color);

    vector< vector<cv::Point> > hulls(contours.size());

    // 计算凸包
    for(int i = 0;i < contours.size();i++){
        cv::convexHull(contours[i],hulls[i]);
    }
    // 绘制凸包
    for(int i = 0;i < hulls.size();i++){
        cv::drawContours(dst, hulls, i, cv::Scalar(0,255,0), 1, 8,
                         vector<cv::Vec4i>(), 0, cv::Point());

    }

    // 显示
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

在这里插入图片描述

8、cv::convexityDefects、cv::isContourConvex


1)cv::convexityDefects:查找轮廓的凸度缺陷。

void cv::convexityDefects(InputArray contour,InputArray convexhull,OutputArray convexityDefects)

参数如下:

参数名称 参数描述
contour 输入轮廓
convexhull 使用convexHull获得的凸包,其中应包含构成该包的轮廓点的索引。
convexityDefects 凸度缺陷的输出向量。

2)cv::isContourConvex:测试轮廓凸度,判断是否凸包。

bool cv::isContourConvex(InputArray contour)

该功能测试输入轮廓是否为凸包。 轮廓必须简单,即没有自相交。 否则,函数输出未定义。

参数contour为二维点的输入向量,存储在std :: vector <>或Mat中。

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

using namespace std;
using namespace cv;

int main(int argc, char* argv[])
{
    Mat *img_01 = new Mat(400, 400, CV_8UC3);
    Mat *img_02 = new Mat(400, 400, CV_8UC3);
    *img_01 = Scalar::all(0);
    *img_02 = Scalar::all(0);
    // 轮廓点组成的数组
    vector<Point> points_01,points_02;


    // 给轮廓组赋值
    points_01.push_back(Point(10, 10));points_01.push_back(Point(10,390));
    points_01.push_back(Point(390, 390));points_01.push_back(Point(150, 250));
    points_02.push_back(Point(10, 10));points_02.push_back(Point(10,390));
    points_02.push_back(Point(390, 390));points_02.push_back(Point(250, 150));

    vector<int> hull_01,hull_02;
    // 计算凸包
    convexHull(points_01, hull_01, true);
    convexHull(points_02, hull_02, true);

    // 绘制轮廓
    for(int i=0;i < 4;++i)
    {
        circle(*img_01, points_01[i], 3, Scalar(0,255,255), -1, LINE_AA);
        circle(*img_02, points_02[i], 3, Scalar(0,255,255), -1, LINE_AA);
    }
    // 绘制凸包轮廓
    Point poi_01 = points_01[hull_01[hull_01.size()-1]];
    for(int i=0;i < hull_01.size();++i)
    {
        line(*img_01, poi_01, points_01[i], Scalar(255,255,0), 1, LINE_AA);
        poi_01 = points_01[i];
    }
    Point poi_02 = points_02[hull_02[hull_02.size()-1]];
    for(int i=0;i < hull_02.size();++i)
    {
        line(*img_02, poi_02, points_02[i], Scalar(255,255,0), 1, LINE_AA);
        poi_02 = points_02[i];
    }

    vector<Vec4i> defects;
    // 如果有凸缺陷就把它画出来
    if( !isContourConvex(points_01) )
    {
        convexityDefects(
                    points_01,
                    Mat(hull_01),
                    defects
                    );
        // 绘制缺陷
        for(int i=0;i < defects.size();++i)
        {
            circle(*img_01, points_01[defects[i][0]], 6, Scalar(255,0,0), 2, LINE_AA);
            circle(*img_01, points_01[defects[i][1]], 6, Scalar(255,0,0), 2, LINE_AA);
            circle(*img_01, points_01[defects[i][2]], 6, Scalar(255,0,0), 2, LINE_AA);
            line(*img_01, points_01[defects[i][0]], points_01[defects[i][1]], Scalar(255,0,0), 1, LINE_AA);
            line(*img_01, points_01[defects[i][1]], points_01[defects[i][2]], Scalar(255,0,0), 1, LINE_AA);
            line(*img_01, points_01[defects[i][2]], points_01[defects[i][0]], Scalar(255,0,0), 1, LINE_AA);
        }
        defects.clear();
    }
    if( !isContourConvex( points_02 ))
    {
        vector<Vec4i> defects;
        convexityDefects(
                    points_01,
                    Mat(hull_01),
                    defects
                    );
        // 绘制出缺陷的轮廓
        for(int i=0;i < defects.size();++i)
        {
            circle(*img_02, points_01[defects[i][0]], 6, Scalar(255,0,0), 2, LINE_AA);
            circle(*img_02, points_01[defects[i][1]], 6, Scalar(255,0,0), 2, LINE_AA);
            circle(*img_02, points_01[defects[i][2]], 6, Scalar(255,0,0), 2, LINE_AA);
            line(*img_02, points_01[defects[i][0]], points_01[defects[i][1]], Scalar(255,0,0), 1, LINE_AA);
            line(*img_02, points_01[defects[i][1]], points_01[defects[i][2]], Scalar(255,0,0), 1, LINE_AA);
            line(*img_02, points_01[defects[i][2]], points_01[defects[i][0]], Scalar(255,0,0), 1, LINE_AA);
        }
        defects.clear();
    }

    imshow("img_01", *img_01);
    imshow("img_02 ", *img_02);
    waitKey();

    return 0;
}

在这里插入图片描述

9、cv::fitEllipse、cv::fitEllipseAMS、cv::fitEllipseDirect


1)cv::fitEllipse:使一组2D点进行拟合椭圆。

RotatedRect cv::fitEllipse(InputArray points)

该函数将计算最适合一组2D点的椭圆(在最小二乘意义上)。 它返回内接椭圆的旋转矩形。 使用【Andrew W Fitzgibbon and Robert B Fisher. A buyer’s guide to conic fitting. In Proceedings of the 6th British conference on Machine vision (Vol. 2), pages 513–522. BMVA Press, 1995.】描述的第一种算法。 开发人员应记住,由于数据点靠近包含Mat元素的边界,返回的ellipse / rotatedRect数据可能包含负索引。

参数points为输入二维点集,存储在std :: vector <>或Mat中

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

using namespace std;

int main()
{
    // 构造图像
    cv::Mat src(512,512,CV_8UC3);

    // 构造点集
    vector<cv::Point> points;
    cv::Point p1(156,256);
    points.push_back(p1);
    cv::Point p2(206,231);
    points.push_back(p2);
    cv::Point p3(256,206);
    points.push_back(p3);
    cv::Point p4(306,231);
    points.push_back(p4);
    cv::Point p5(356,256);
    points.push_back(p5);
    cv::Point p6(306,287);
    points.push_back(p6);
    cv::Point p7(256,306);
    points.push_back(p7);
    cv::Point p8(206,287);
    points.push_back(p8);

    // 绘制点
    for(int i = 0;i < points.size();i++){
        cv::circle(src,points[i],3,cv::Scalar(0,0,255));
    }

    // 拟合椭圆,返回包围旋转矩形
    cv::RotatedRect box = cv::fitEllipse(points);
    // 通过包围旋转矩形绘制椭圆
    cv::ellipse(src, box, cv::Scalar(0,255,0), 3, cv::LINE_AA);

    // 显示图像
    cv::imshow("src",src);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

2)cv::fitEllipseAMS:使一组2D点进行拟合椭圆。

RotatedRect cv::fitEllipseAMS(InputArray points)

该函数计算适合一组2D点的椭圆。 它返回内接椭圆的旋转矩形。 使用【Gabriel Taubin. Estimation of planar curves, surfaces, and nonplanar space curves defined by implicit equations with applications to edge and range image segmentation. IEEE Transactions on Pattern Analysis and Machine Intelligence, 13(11):1115–1138, 1991.】提出的近似均方(AMS)。

对于椭圆,此基础集为: χ = ( x 2 , x y , y 2 , x , y , 1 ) \chi= \left(x^2, x y, y^2, x, y, 1\right) χ=(x2,xy,y2,x,y,1)这是一组六个自由系数 A T = { A xx , A xy , A yy , A x , A y , A 0 } A^T=\left\{A_{\text{xx}},A_{\text{xy}},A_{\text{yy}},A_x,A_y,A_0\right\} AT={ Axx,Axy,Ayy,Ax,Ay,A0}。但是,要指定一个椭圆,只需要五个数字即可。长轴和短轴的长度 ( a , b ) (a,b) (a,b),位置 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)和方向 θ \theta θ这是因为基础集尽可能包含线,二次函数,抛物线和双曲线函数以及椭圆函数。如果发现拟合为抛物线或双曲线函数,则使用标准fitEllipse方法。AMS方法通过施加以下条件将拟合限制为抛物线,双曲线和椭圆曲线: A T ( D x T D x + D y T D y ) A = 1 A^T ( D_x^T D_x + D_y^T D_y) A = 1 AT(DxTDx+DyTDy)A=1其中矩阵 D x D_x Dx D y D_y Dy是设计矩阵D相对于x和y的偏导数。矩阵逐行形成,将以下内容应用于集合中的每个点:

KaTeX parse error: No such environment: align* at position 7: \begin{̲a̲l̲i̲g̲n̲*̲}̲ D(i,:)&=\left\…

AMS方法使损失函数最小化:

KaTeX parse error: No such environment: equation* at position 7: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲ \epsilon ^2=\f…

通过解决广义特征值问题可以找到最小损失。

KaTeX parse error: No such environment: equation* at position 7: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲ D^T D A = \lam…

参数points为输入二维点集,存储在std :: vector <>或Mat中。

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

using namespace std;

int main()
{
    // 构造图像
    cv::Mat src(512,512,CV_8UC3);

    // 构造点集
    vector<cv::Point> points;
    cv::Point p1(156,256);
    points.push_back(p1);
    cv::Point p2(206,231);
    points.push_back(p2);
    cv::Point p3(256,206);
    points.push_back(p3);
    cv::Point p4(306,231);
    points.push_back(p4);
    cv::Point p5(356,256);
    points.push_back(p5);
    cv::Point p6(306,287);
    points.push_back(p6);
    cv::Point p7(256,306);
    points.push_back(p7);
    cv::Point p8(206,287);
    points.push_back(p8);

    // 绘制点
    for(int i = 0;i < points.size();i++){
        cv::circle(src,points[i],3,cv::Scalar(0,0,255));
    }

    // 拟合椭圆,返回包围旋转矩形
    cv::RotatedRect box = cv::fitEllipseAMS(points);
    // 通过包围旋转矩形绘制椭圆
    cv::ellipse(src, box, cv::Scalar(0,255,0), 3, cv::LINE_AA);

    // 显示图像
    cv::imshow("src",src);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

3)cv::fitEllipseDirect:使一组2D点进行拟合椭圆。

RotatedRect cv::fitEllipseDirect(InputArray points)**

该函数计算适合一组2D点的椭圆。 它返回内接椭圆的旋转矩形。 使用【Andrew Fitzgibbon, Maurizio Pilu, and Robert B. Fisher. Direct least square fitting of ellipses. IEEE Transactions on Pattern Analysis and Machine Intelligence, 21(5):476–480, 1999.】的直接最小二乘法(Direct)。

对于椭圆,此基础集为: χ = ( x 2 , x y , y 2 , x , y , 1 ) \chi= \left(x^2, x y, y^2, x, y, 1\right) χ=(x2,xy,y2,x,y,1)这是一组六个自由系数 A T = { A xx , A xy , A yy , A x , A y , A 0 } A^T=\left\{A_{\text{xx}},A_{\text{xy}},A_{\text{yy}},A_x,A_y,A_0\right\} AT={ Axx,Axy,Ayy,Ax,Ay,A0}。但是,要指定一个椭圆,只需要五个数字即可。长轴和短轴的长度 ( a , b ) (a,b) (a,b),位置 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)和方向 θ \theta θ这是因为基础集尽可能包含线,二次函数,抛物线和双曲线函数以及椭圆函数。Direct方法通过确保以下各项将拟合限制在椭圆范围内: 4 A x x A y y − A x y 2 > 0 4 A_{xx} A_{yy}- A_{xy}^2 > 0 4AxxAyyAxy2>0。施加的条件是 4 A x x A y y − A x y 2 = 1 4 A_{xx} A_{yy}- A_{xy}^2=1 4AxxAyyAxy2=1满足不等式,并且系数可以任意缩放,这并不是过分的限制。

KaTeX parse error: No such environment: equation* at position 7: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲ \epsilon ^2= A…

通过解决广义特征值问题可以找到最小损失。

KaTeX parse error: No such environment: equation* at position 7: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲ D^T D A = \lam…

系统仅产生一个正特征值 λ \lambda λ,该特征值被选择为其特征向量 u u u作为解。 这些用于查找系数

KaTeX parse error: No such environment: equation* at position 7: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲ A = \sqrt{\fra…

比例因子保证 A T C A = 1 A^T C A =1 ATCA=1

参数points为输入二维点集,存储在std :: vector <>或Mat中。

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

using namespace std;

int main()
{
    // 构造图像
    cv::Mat src(512,512,CV_8UC3);

    // 构造点集
    vector<cv::Point> points;
    cv::Point p1(156,256);
    points.push_back(p1);
    cv::Point p2(206,231);
    points.push_back(p2);
    cv::Point p3(256,206);
    points.push_back(p3);
    cv::Point p4(306,231);
    points.push_back(p4);
    cv::Point p5(356,256);
    points.push_back(p5);
    cv::Point p6(306,287);
    points.push_back(p6);
    cv::Point p7(256,306);
    points.push_back(p7);
    cv::Point p8(206,287);
    points.push_back(p8);

    // 绘制点
    for(int i = 0;i < points.size();i++){
        cv::circle(src,points[i],3,cv::Scalar(0,0,255));
    }

    // 拟合椭圆,返回包围旋转矩形
    cv::RotatedRect box = cv::fitEllipseDirect(points);
    // 通过包围旋转矩形绘制椭圆
    cv::ellipse(src, box, cv::Scalar(0,255,0), 3, cv::LINE_AA);

    // 显示图像
    cv::imshow("src",src);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

10、cv::fitLine


使用2D或3D点集拟合直线

void cv::fitLine(InputArray points,OutputArray line,int distType,double param,double reps,double aeps)

函数fitLine通过最小化将线拟合到2D或3D点集 ∑ i ρ ( r i ) \sum_i \rho(r_i) iρ(ri)其中 r i r_i ri是第i个点,直线和 ρ ( r ) \rho(r) ρ(r)之间的距离,是距离函数,其中之一:

  • DIST_L2 ρ ( r ) = r 2 / 2 (最简单最快的最小二乘法) \rho (r) = r^2/2 \quad \text{(最简单最快的最小二乘法)} ρ(r)=r2/2(最简单最快的最小二乘法)
  • DIST_L1 ρ ( r ) = r \rho (r) = r ρ(r)=r
  • DIST_L12 ρ ( r ) = 2 ⋅ ( 1 + r 2 2 − 1 ) \rho (r) = 2 \cdot ( \sqrt{1 + \frac{r^2}{2}} - 1) ρ(r)=2(1+2r2 1)
  • DIST_FAIR ρ ( r ) = C 2 ⋅ ( r C − log ⁡ ( 1 + r C ) ) where C = 1.3998 \rho \left (r \right ) = C^2 \cdot \left ( \frac{r}{C} - \log{\left(1 + \frac{r}{C}\right)} \right ) \quad \text{where} \quad C=1.3998 ρ(r)=C2(Crlog(1+Cr))whereC=1.3998
  • DIST_WELSCH ρ ( r ) = C 2 2 ⋅ ( 1 − exp ⁡ ( − ( r C ) 2 ) ) where C = 2.9846 \rho \left (r \right ) = \frac{C^2}{2} \cdot \left ( 1 - \exp{\left(-\left(\frac{r}{C}\right)^2\right)} \right ) \quad \text{where} \quad C=2.9846 ρ(r)=2C2(1exp((Cr)2))whereC=2.9846
  • DIST_HUBER ρ ( r ) = { r 2 / 2 i f ( r < C ) C ⋅ ( r − C / 2 ) o t h e r w i s e where C = 1.345 \rho (r) = \begin{cases}{r^2/2}&{if (r < C)}\\{C \cdot (r-C/2)}&{otherwise} \quad \text{where} \quad C=1.345\end{cases} ρ(r)={ r2/2C(rC/2)if(r<C)otherwisewhereC=1.345

该算法基于M-estimator(http://en.wikipedia.org/wiki/M-estimator)技术,该技术使用加权最小二乘算法迭代拟合线。 在每次迭代之后,将权重 w i w_i wi调整为与 ρ ( r i ) \rho(r_i) ρ(ri)成反比。

参数如下:

参数名称 参数描述
points 输入二维点集,存储在std :: vector <>或Mat中。
line 输出直线参数。如果是2D拟合,则它应该是4个元素的向量(如Vec4f)-(vx,vy,x0,y0),其中(vx,vy)是与线共线的归一化向量,而(x0,y0)是 线上的一点。 如果是3D拟合,则它应该是6个元素的向量(如Vec6f)-(vx,vy,vz,x0,y0,z0),其中(vx,vy,vz)是与线共线的归一化向量,并且 (x0,y0,z0)是直线上的一个点。
distType M-estimator算法距离估算, 请参见 DistanceTypes
param 某些距离类型的数值参数(C)。 如果为0,则选择一个最佳值。
reps 半径的足够精度(坐标原点和直线之间的距离)。
aeps 角度精度足够。 对于reps和aeps,0.01将是一个很好的默认值。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //创建一个用于绘制图像的空白图
    cv::Mat image = cv::Mat::zeros(480, 640, CV_8UC3);

    //输入拟合点
    std::vector<cv::Point> points;
    points.push_back(cv::Point(48, 58));
    points.push_back(cv::Point(105, 98));
    points.push_back(cv::Point(155, 160));
    points.push_back(cv::Point(212, 220));
    points.push_back(cv::Point(248, 260));
    points.push_back(cv::Point(320, 300));
    points.push_back(cv::Point(350, 360));
    points.push_back(cv::Point(412, 400));

    //将拟合点绘制到空白图上
    for (int i = 0; i < points.size(); i++)
    {
        cv::circle(image, points[i], 5, cv::Scalar(0, 0, 255), 2, 8, 0);
    }

    cv::Vec4f line_para;
    cv::fitLine(points, line_para, cv::DIST_L2, 0, 1e-2, 1e-2);

    std::cout << "line_para = " << line_para << std::endl;

    //获取点斜式的点和斜率
    cv::Point point0;
    point0.x = line_para[2];
    point0.y = line_para[3];

    double k = line_para[1] / line_para[0];

    //计算直线的端点(y = k(x - x0) + y0)
    cv::Point point1, point2;
    point1.x = 0;
    point1.y = k * (0 - point0.x) + point0.y;
    point2.x = 640;
    point2.y = k * (640 - point0.x) + point0.y;

    cv::line(image, point1, point2, cv::Scalar(0, 255, 0), 2, 8, 0);

    cv::imshow("image", image);
    cv::waitKey(0);
    return 0;
}

在这里插入图片描述

11、cv::HuMoments、cv::moments


1)cv::HuMoments:计算七个Hu不变量。

该函数计算七个Hu矩不变式(在【Ming-Kuei Hu. Visual pattern recognition by moment invariants. Information Theory, IRE Transactions on, 8(2):179–187, 1962.】中引入;另请参见http://en.wikipedia.org/wiki/Image_moment),其定义为:

h u [ 0 ] = η 20 + η 02 h u [ 1 ] = ( η 20 − η 02 ) 2 + 4 η 11 2 h u [ 2 ] = ( η 30 − 3 η 12 ) 2 + ( 3 η 21 − η 03 ) 2 h u [ 3 ] = ( η 30 + η 12 ) 2 + ( η 21 + η 03 ) 2 h u [ 4 ] = ( η 30 − 3 η 12 ) ( η 30 + η 12 ) [ ( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] + ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] h u [ 5 ] = ( η 20 − η 02 ) [ ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] + 4 η 11 ( η 30 + η 12 ) ( η 21 + η 03 ) h u [ 6 ] = ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] − ( η 30 − 3 η 12 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] \begin{array}{l} hu[0]= \eta _{20}+ \eta _{02} \\ hu[1]=( \eta _{20}- \eta _{02})^{2}+4 \eta _{11}^{2} \\ hu[2]=( \eta _{30}-3 \eta _{12})^{2}+ (3 \eta _{21}- \eta _{03})^{2} \\ hu[3]=( \eta _{30}+ \eta _{12})^{2}+ ( \eta _{21}+ \eta _{03})^{2} \\ hu[4]=( \eta _{30}-3 \eta _{12})( \eta _{30}+ \eta _{12})[( \eta _{30}+ \eta _{12})^{2}-3( \eta _{21}+ \eta _{03})^{2}]+(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ hu[5]=( \eta _{20}- \eta _{02})[( \eta _{30}+ \eta _{12})^{2}- ( \eta _{21}+ \eta _{03})^{2}]+4 \eta _{11}( \eta _{30}+ \eta _{12})( \eta _{21}+ \eta _{03}) \\ hu[6]=(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}]-( \eta _{30}-3 \eta _{12})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ \end{array} hu[0]=η20+η02hu[1]=(η20η02)2+4η112hu[2]=(η303η12)2+(3η21η03)2hu[3]=(η30+η12)2+(η21+η03)2hu[4]=(η303η12)(η30+η12)[(η30+η12)23(η21+η03)2]+(3η21η03)(η21+η03)[3(η30+η12)2(η21+η03)2]hu[5]=(η20η02)[(η30+η12)2(η21+η03)2]+4η11(η30+η12)(η21+η03)hu[6]=(3η21η03)(η21+η03)[3(η30+η12)2(η21+η03)2](η303η12)(η21+η03)[3(η30+η12)2(η21+η03)2]

其中 η j i \eta_{ji} ηji代表 M o m e n t s : : n u j i Moments :: nu_{ji} Moments::nuji

这些值被证明对图像比例,旋转和反射率是不变的,但第七个值因反射而改变。 假设图像分辨率无限大,就证明了这种不变性。 对于光栅图像,原始图像和变换后图像的计算出的Hu不变量有些不同。

参数如下:

参数名称 参数描述
moments 用弯矩计算输入弯矩。
hu 输出Hu不变量。

2)cv::moments:计算图像中心矩(最高到三阶)。

Moments cv::moments (InputArray array,bool binaryImage = false)

该函数计算矢量形状或栅格化形状的矩,直到三阶。 结果在结构cv :: Moments中返回。

参数如下:

参数名称 参数描述
array 输入阵列,可以是光栅影象(单通道,8-bit或浮点型二维阵列),或者是一个二维阵列(1 XN或NX 1),二维阵列型别为Point或Point2f
binaryImage 预设值是false,如果为true,则所有非零的画素都会按值1对待,也就是说相当于对影象进行了二值化处理,阈值为1,此引数仅对图像有效。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-1.png");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE );
    cv::Scalar color(255,0,255);

    // 计算图像的矩
    vector<cv::Moments>mu(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        mu[i] = moments(contours[i], false);
    }

    // 计算图像质心
    vector<cv::Point2f>mc(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        mc[i] = cv::Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
        // 绘制质心
        cv::circle(dst, mc[i], 4, cv::Scalar(0,0,255), -1);
    }

    //绘制轮廓
    cv::drawContours( dst, contours,-1, color);

    // 显示图像
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

12、cv::intersectConvexConvex


查找两个凸多边形的交集。

float cv::intersectConvexConvex(InputArray _p1,InputArray _p2,OutputArray _p12,bool handleNested = true)

参数如下:

参数名称 参数描述
_p1 第一个多边形
_p2 第二个多边形
_p12 输出描述相交区域的多边形
handleNested 为true时,如果其中一个多边形完全封闭在另一个多边形中,则找到相交。 如果为false,则找不到交集。 如果多边形共享一个边,或者一个多边形的顶点位于另一个多边形的边缘,则不将其视为嵌套的,并且无论handleNested的值如何,都将找到一个相交。

函数返回相交多边形面积的绝对值。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

static float drawIntersection(Mat &image, vector<Point> polygon1, vector<Point> polygon2, bool handleNested = true)
{
    vector<Point> intersectionPolygon;
    vector<vector<Point> > polygons;
    polygons.push_back(polygon1);
    polygons.push_back(polygon2);
    float intersectArea = intersectConvexConvex(polygon1, polygon2, intersectionPolygon, handleNested);
    if (intersectArea > 0)
    {
        Scalar fillColor(200, 200, 200);
        // If the input is invalid, draw the intersection in red
        if (!isContourConvex(polygon1) || !isContourConvex(polygon2))
        {
            fillColor = Scalar(0, 0, 255);
        }
        fillPoly(image, intersectionPolygon, fillColor);
    }
    polylines(image, polygons, true, Scalar(0, 0, 0));
    return intersectArea;
}

int main()
{
    cv::Mat src(512,512,CV_8UC3);
    std::vector<Point> pt1(4), pt2(4), pt12;
    pt1[0] = Point(80, 150);
    pt1[1] = Point(200, 150);
    pt1[3] = Point(80, 300);
    pt1[2] = Point(200, 300);

    pt2[0] = Point(80, 150);
    pt2[1] = Point(200, 150);
    pt2[3] = Point(80,300);
    pt2[2] = Point(200, 300);
    drawIntersection(src,pt1,pt2);
    cv::imshow("src",src);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

13、cv::minEnclosingCircle、cv::minEnclosingTriangle


1)cv::minEnclosingCircle:查找包含2D点集的最小面积的圆。

void cv::minEnclosingCircle(InputArray points,Point2f& center,float & radius)

该函数使用迭代算法找到2D点集的最小封闭圆。

参数名称 参数描述
points 输入二维点集,存储在std :: vector <>或Mat中
center 输出圆心
radius 输出圆半径
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/regions.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE );
    cv::Scalar color(0,0,255);

    for(int i = 0;i < contours.size();i++){
        cv::Point2f center;
        float radius = 0;
        cv::minEnclosingCircle(contours[i],center,radius);
         cv::circle(dst,center,radius,cv::Scalar(0,255,0),1);
    }

    // 绘制轮廓
    cv::drawContours( dst, contours,-1, color);

    // 显示图像
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

2)cv::minEnclosingTriangle:查找包含2D点集的最小面积的三角形,并返回其面积。

double cv::minEnclosingTriangle(InputArray points,OutputArray triangle)

该函数找到包围给定2D点集的最小面积的三角形,并返回其面积。 下图显示了给定2D点集的输出。 2D点以红色*表示,而包围的三角形以黄色表示。

在这里插入图片描述

参数如下:

参数名称 参数描述
points Input vector of 2D points with depth CV_32S or CV_32F, stored in std::vector<> or Mat
triangle 定义三角形顶点的三个2D点的输出向量。 OutputArray的深度必须为CV_32F。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-3.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_TREE, cv::CHAIN_APPROX_NONE );
    cv::Scalar color(0,0,255);

    for(int i = 0;i < contours.size();i++){
        vector<cv::Point2f> triangle;
        cv::minEnclosingTriangle(contours[i],triangle);
        cout << triangle.size() << endl;
        // 绘制三角形
        for(int j = 0;j < 3;j++){
           cv::line(dst, triangle[j], triangle[(j+1)%3], cv::Scalar(255, 255, 0), 1, cv::LINE_AA);
        }
    }

    // 绘制轮廓
    cv::drawContours( dst, contours,-1, color);

    // 显示图像
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

在这里插入图片描述

14、cv::pointPolygonTest


执行轮廓点测试。

double cv::pointPolygonTest(InputArray contour,Point2f pt,bool measureDist)

该函数确定该点是在轮廓内部,外部还是在边缘上(或与顶点重合)。 它分别返回正(内部),负(外部)或零(边缘)值。 当measureDist = false时,返回值分别为+ 1,-1和0。 否则,返回值是该点与最近的轮廓边之间的有符号距离。

参数如下:

参数名称 参数描述
contour 输入轮廓
pt 针对轮廓点进行测试。
measureDist 如果为true,则该函数会估计从该点到最近的轮廓边的有符号距离。 否则,该功能仅检查该点是否在轮廓内。
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace cv;
using namespace std;

/**
 * @function main
 */
int main( void )
{
    //读取图片
    cv::Mat src = cv::imread("images/shape-2.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
    }

    // 转换成图像
    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);

    vector<vector<cv::Point> > contours;
    vector<cv::Vec4i> hierarchy;

    // 阈值化图像
    cv::threshold(gray, gray, 200, 255, cv::THRESH_BINARY_INV);

    // 查找轮廓
    cv::findContours( gray, contours, hierarchy,
                      cv::RETR_TREE, cv::CHAIN_APPROX_NONE );

    /// Calculate the distances to the contour
    Mat raw_dist( src.size(), CV_32F );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            raw_dist.at<float>(i,j) = (float)pointPolygonTest( contours[0], Point2f((float)j, (float)i), true );
        }
    }

    double minVal, maxVal;
    Point maxDistPt; // inscribed circle center
    minMaxLoc(raw_dist, &minVal, &maxVal, NULL, &maxDistPt);
    minVal = abs(minVal);
    maxVal = abs(maxVal);

    /// Depicting the  distances graphically
    Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            if( raw_dist.at<float>(i,j) < 0 )
            {
                drawing.at<Vec3b>(i,j)[0] = (uchar)(255 - abs(raw_dist.at<float>(i,j)) * 255 / minVal);
            }
            else if( raw_dist.at<float>(i,j) > 0 )
            {
                drawing.at<Vec3b>(i,j)[2] = (uchar)(255 - raw_dist.at<float>(i,j) * 255 / maxVal);
            }
            else
            {
                drawing.at<Vec3b>(i,j)[0] = 255;
                drawing.at<Vec3b>(i,j)[1] = 255;
                drawing.at<Vec3b>(i,j)[2] = 255;
            }
        }
    }
    circle(drawing, maxDistPt, (int)maxVal, Scalar(255,255,255));

    /// Show your results
    imshow( "Source", src );
    imshow( "Distance and inscribed circle", drawing );

    waitKey();
    return 0;
}

在这里插入图片描述

15、cv::rotatedRectangleIntersection


找出两个旋转的矩形之间是否有任何交集。

int cv::rotatedRectangleIntersection(const RotatedRect & rect1,const RotatedRect & rect2,OutputArray intersectingRegion)

如果存在,则还返回相交区域的顶点。

以下是路口配置的一些示例。 阴影图案表示相交区域,红色顶点由该函数返回。

在这里插入图片描述

rect1 = ((50,50), (100,100), 0)  # x,y  w,h
 
rect2 = ((90,100), (50,2), 60)
 
r1 = cv2.rotatedRectangleIntersection(rect1, rect2)  # 区分正负角度,逆时针为负,顺时针为正
 
order_pts = cv2.convexHull(r1[1], returnPoints=True)
 
int_area = cv2.contourArea(order_pts)
print(int_area)

猜你喜欢

转载自blog.csdn.net/wujuxKkoolerter/article/details/112426651
今日推荐