OPENCV图像轮廓检测

前面在图像转换的时候学到canny算子,可以检测出图像的轮廓信息,但是,该算子检测到的轮廓信息还需要我们手动的用眼睛去识别,而实际工程应用中,我们需要得到轮廓的具体数学信息,这就涉及到今天的主题,图像轮廓检测.

         一.图像轮廓检测

         在opencv中,轮廓对应着一系列的点的集合,opencv提供了一个函数,用来获得这些点的集合

         API:void finContours(输入图像,输出轮廓点集,输出向量,int 轮廓检索模式,int 轮廓近似方法,Point 轮廓点的可选偏移量)

         注:1.输入图像,是单通道八位阈值化图像,也就是对应canny检测之后的图像,如果不是阈值化图像,算法会将图像中不为0的点当成1,0点当成0处理,我们可以通过canny threshold adaptiveThreshold来处理,

            2.该函数在检测轮廓的时候会修改源图像的数据,所以最好不要拿源图像直接检测轮廓,最好是用拷贝

            3.输出点集的形式为vector<vector<Point>>contours,每个向量包含有多个向量,包含的向量一个就是一条轮廓,而包含的向量是由一个个点构成的.

                  4.输出向量包含了图像轮廓的拓扑信息,比如这个轮廓的前一个轮廓编号,后一个轮廓编号,父轮廓编号以及子轮廓编号,形式为vector<vec4i>h,h[0]为后一个轮廓编号 h[1]后一个轮廓编号 h[2]父轮廓编号 h[3]内嵌轮廓编号

                  5.轮廓的检索模式包含有如下几种选择RETR_EXTERNAL只检测最外围的轮廓  RETR_LIST提取所有的轮廓,不建立上下等级关系,只有兄弟等级关系  RETR_CCOMP提取所有轮廓,建立为双层结构 RETR_TREE提取所有轮廓,建立网状结构

                  6.轮廓的近似方法有以下取值 CHAIN_APPROX_NONE获取轮廓的每一个像素,像素的最大间距不超过1 CHAIN_APPROX_SIMPLE压缩水平垂直对角线的元素,只保留该方向的终点坐标(也就是说一条中垂线a-b,中间的点被忽略了) CHAIN_APPROX_TC89_LI使用TEH_CHAIN逼近算法中的LI算法  CHAIN_APPROX_TC89_KCOS使用TEH_CHAIN逼近算法中的KCOS算法

                  7.可选偏移量,对ROI区域中获得的轮廓要在整个图像中分析的时候,该参数可以派上用场

         二.图像轮廓绘制

         该函数可以很方便的绘制出我们找到的轮廓点集和拓扑结构构成的轮廓图.

         API:void drawContours(源图像,轮廓点集,int 绘制指示,scalar 轮廓颜色,int 轮廓粗细,int 轮廓线形,inputarray 轮廓的拓扑结构信息,int 轮廓的最大绘制等级,int 可选的轮廓偏移参数)

         注:1.如果绘制指示为负值,则绘制所有轮廓,轮廓粗细默认值为1,为负值的化,绘制在轮廓的内部进行,使用filled,填充轮廓,线条类型默认为8,LINE_AA将绘制抗锯齿的线型,轮廓绘制等级,默认值是INT_MAX,指示最多可以绘制几层轮廓,=.

         轮廓检测的基本步骤为1.图像转换为灰度图像,2.图像的滤波,降噪,3.图像的canny二值化或者其他二值化方式,4.寻找轮廓findContours

代码例子如下

//边À?缘¦Ì轮?廓¤a查¨¦找¨°
//先¨¨进?行D边À?缘¦Ì滤?波¡§ 然¨?后¨®用®?canny算?法¤¡§得Ì?到Ì?二t值¦Ì化¡¥图ª?像?
//滤?波¡§算?法¤¡§选?择?均¨´值¦Ì滤?波¡§ 可¨¦调Ì¡Â的Ì?是º?孔¡Á径?尺?寸ä?
//canny算?法¤¡§可¨¦调Ì¡Â的Ì?上¦?下?限T和¨ªsobel算?子Á¨®孔¡Á径?3 5 7
Mat srcImage,blurImage,grayBlurImage,cannyImage,contourImage;
vector<vector<Point>>g_vContours;
vector<Vec4i>g_vHierarchy;
RNG g_rng(12345);

const int g_BlurMax = 100;
int g_BlurValue;
void onBlurTrackBar(int pos,void* userData);

const int g_sobelSizeMax = 2;//sobel孔¡Á径?
int g_sobelValue;

const int g_lowThresholdMax = 80;//边À?缘¦Ì检¨¬测a低̨ª阈D值¦Ì
int g_lowThresholdValue;
int g_upThresholdValue;

void onTrackBarSobelSize(int pos,void* userData);
void onTrackBarLowThresholdSize(int pos,void* userData);


int main(int argc,char* argv[])
{
    srcImage = imread("F:\\opencv\\OpenCVImage\\findContours.jpg");

    g_BlurValue = 4;
    g_sobelValue = 1;
    g_lowThresholdValue = 80;
    g_upThresholdValue = 240;
    namedWindow("canny Image");
    namedWindow("contours Image");
    createTrackbar("blur size value ", "canny Image", &g_BlurValue, g_BlurMax,onBlurTrackBar,0);
    createTrackbar("sobel size", "canny Image", &g_sobelValue, g_sobelSizeMax,onTrackBarSobelSize,0);
    createTrackbar("low threshold", "canny Image", &g_lowThresholdValue, g_lowThresholdMax,onTrackBarLowThresholdSize,0);
    onBlurTrackBar(g_BlurValue, 0);


    imshow("src image", srcImage);
    moveWindow("src image", 0, 0);
    moveWindow("canny Image", srcImage.cols, 0);
    moveWindow("contour image", srcImage.cols*2, 0);

    waitKey(0);
    return 0;
}

//修T改?了¢?滤?波¡§参?数ºy
void onBlurTrackBar(int pos,void* userData)
{
    int blurSize = g_BlurValue*2+1;
    blur(srcImage, blurImage, Size(blurSize,blurSize));

    int sobelValue = g_sobelValue*2 +3;
    if (g_lowThresholdValue == 0) {
        g_lowThresholdValue = 1;
    }
    int lowThresholdValue = g_lowThresholdValue;
    int upThresholdValue = lowThresholdValue*3;
    cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
    //计?算?canny
    Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

    contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
    findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
    for(int i = 0;i<g_vHierarchy.size();i++)
    {
        Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
        drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
    }

    imshow("contour image", contourImage);
    imshow("canny Image",cannyImage);
}

//修T改?了¢?sobel孔¡Á径?参?数ºy
void onTrackBarSobelSize(int pos,void* userData)
{
    int blurSize = g_BlurValue*2+1;
    blur(srcImage, blurImage, Size(blurSize,blurSize));

    int sobelValue = g_sobelValue*2 +3;
    if (g_lowThresholdValue == 0) {
        g_lowThresholdValue = 1;
    }
    int lowThresholdValue = g_lowThresholdValue;
    int upThresholdValue = lowThresholdValue*3;
    cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
    //计?算?canny
    Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

    contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
    findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
    for(int i = 0;i<g_vHierarchy.size();i++)
    {
        Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
        drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
    }

    imshow("contour image", contourImage);
    imshow("canny Image",cannyImage);
}

//修T改?了¢?阈D值¦Ì参?数ºy
void onTrackBarLowThresholdSize(int pos,void* userData)
{
    int blurSize = g_BlurValue*2+1;
    blur(srcImage, blurImage, Size(blurSize,blurSize));

    int sobelValue = g_sobelValue*2 +3;
    if (g_lowThresholdValue == 0) {
        g_lowThresholdValue = 1;
    }
    int lowThresholdValue = g_lowThresholdValue;
    int upThresholdValue = lowThresholdValue*3;
    cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
    //计?算?canny
    Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

    contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
    findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
    for(int i = 0;i<g_vHierarchy.size();i++)
    {
        Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
        drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
    }

    imshow("contour image", contourImage);
    imshow("canny Image",cannyImage);
}

二.凸包

         凸包是指,给定一个二维平面上的点集,凸包就是将这个点集最外层的点连接起来的构成的凸多边形

         计算一个物体的凸包,然后计算凸包的凹缺陷,是理解物体轮廓与形状的有效方式,可以用于典型的相似物体查找中.

         API:void convertHull(输入二维点集,输出凸包,bool 操作方向标识符,bool 返回点类型)

         注:1.操作方向标识符,是指在笛卡尔坐标中,当为true的时候,起始点到结束点顺时针,否则,逆时针.

                  2返回点类型为真时,返回凸包的各个定点,否则,返回凸包各点的指数,当输出为vector<point>时,这个参数被忽略.

                  3.返回的二维点集形态类是vector<point>,得到的凸包类型,也是vector<point>hull,hull.size()是凸包的点的个数.

         一般寻找凸包,主要是先对图像二值化,后寻找轮廓,然后寻找一条指定轮廓的凸包.

示例代码如下

//对?一°?张?图ª?片?进?行D轮?廓¤a查¨¦找¨°,并¡é对?每?一°?个?查¨¦找¨°出?来¤¡ä的Ì?轮?廓¤a运?行D凸ª1包㨹查¨¦找¨°程¨¬序¨°,生¦¨²成¨¦最Á?终?显?示º?图ª?片?
//边À?缘¦Ì轮?廓¤a查¨¦找¨°
//先¨¨进?行D边À?缘¦Ì滤?波¡§ 然¨?后¨®用®?canny算?法¤¡§得Ì?到Ì?二t值¦Ì化¡¥图ª?像?
//滤?波¡§算?法¤¡§选?择?均¨´值¦Ì滤?波¡§ 可¨¦调Ì¡Â的Ì?是º?孔¡Á径?尺?寸ä?
//canny算?法¤¡§可¨¦调Ì¡Â的Ì?上¦?下?限T和¨ªsobel算?子Á¨®孔¡Á径?3 5 7
Mat srcImage,blurImage,grayBlurImage,cannyImage,contourImage,hullImage;
vector<vector<Point>>g_vContours;
vector<Vec4i>g_vHierarchy;
RNG g_rng(12345);

const int g_BlurMax = 100;
int g_BlurValue;
void onBlurTrackBar(int pos,void* userData);

const int g_sobelSizeMax = 2;//sobel孔¡Á径?
int g_sobelValue;

const int g_lowThresholdMax = 80;//边À?缘¦Ì检¨¬测a低̨ª阈D值¦Ì
int g_lowThresholdValue;
int g_upThresholdValue;

void onTrackBarSobelSize(int pos,void* userData);
void onTrackBarLowThresholdSize(int pos,void* userData);


int main(int argc,char* argv[])
{
   srcImage = imread("F:\\opencv\\OpenCVImage\\convexHull.jpg");
   
   g_BlurValue = 4;
   g_sobelValue = 1;
   g_lowThresholdValue = 80;
   g_upThresholdValue = 240;
   namedWindow("canny Image");
   namedWindow("contours Image");
   namedWindow("hull image");
   createTrackbar("blur size value ", "canny Image", &g_BlurValue, g_BlurMax,onBlurTrackBar,0);
   createTrackbar("sobel size", "canny Image", &g_sobelValue, g_sobelSizeMax,onTrackBarSobelSize,0);
   createTrackbar("low threshold", "canny Image", &g_lowThresholdValue, g_lowThresholdMax,onTrackBarLowThresholdSize,0);
   onBlurTrackBar(g_BlurValue, 0);

   moveWindow("src image", 0, 0);
   moveWindow("canny Image", srcImage.cols, srcImage.rows);
   moveWindow("contour image", srcImage.cols*2, 0);
   moveWindow("hull image", srcImage.cols*2, srcImage.rows);

   waitKey(0);
   return 0;
}

//修T改?了¢?滤?波¡§参?数ºy
void onBlurTrackBar(int pos,void* userData)
{
   imshow("src image", srcImage);
   
   int blurSize = g_BlurValue*2+1;
   blur(srcImage, blurImage, Size(blurSize,blurSize));

   int sobelValue = g_sobelValue*2 +3;
   if (g_lowThresholdValue == 0) {
       g_lowThresholdValue = 1;
   }
   int lowThresholdValue = g_lowThresholdValue;
   int upThresholdValue = lowThresholdValue*3;
   cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
   //计?算?canny
   Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

   contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
   for(int i = 0;i<g_vHierarchy.size();i++)
   {
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
   }
   
   vector<vector<Point>>hull(g_vContours.size());
   hullImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   for(int i = 0; i < g_vContours.size(); i++)
   {
       convexHull(Mat(g_vContours[i]), hull[i],false);
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(hullImage, hull, i, color,1,8,vector<Vec4i>(),0,Point(0,0));
   }

   imshow("hull image", hullImage);
   imshow("contour image", contourImage);
   imshow("canny Image",cannyImage);
}

//修T改?了¢?sobel孔¡Á径?参?数ºy
void onTrackBarSobelSize(int pos,void* userData)
{
   imshow("src image", srcImage);

   int blurSize = g_BlurValue*2+1;
   blur(srcImage, blurImage, Size(blurSize,blurSize));

   int sobelValue = g_sobelValue*2 +3;
   if (g_lowThresholdValue == 0) {
       g_lowThresholdValue = 1;
   }
   int lowThresholdValue = g_lowThresholdValue;
   int upThresholdValue = lowThresholdValue*3;
   cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
   //计?算?canny
   Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

   contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
   for(int i = 0;i<g_vHierarchy.size();i++)
   {
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
   }
   vector<vector<Point>>hull(g_vContours.size());
   hullImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   for(int i = 0; i < g_vContours.size(); i++)
   {
       convexHull(Mat(g_vContours[i]), hull[i],false);
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(hullImage, hull, i, color,1,8,vector<Vec4i>(),0,Point(0,0));
   }
   
   imshow("hull image", hullImage);
   imshow("contour image", contourImage);
   imshow("canny Image",cannyImage);
}

//修T改?了¢?阈D值¦Ì参?数ºy
void onTrackBarLowThresholdSize(int pos,void* userData)
{
   imshow("src image", srcImage);

   int blurSize = g_BlurValue*2+1;
   blur(srcImage, blurImage, Size(blurSize,blurSize));

   int sobelValue = g_sobelValue*2 +3;
   if (g_lowThresholdValue == 0) {
       g_lowThresholdValue = 1;
   }
   int lowThresholdValue = g_lowThresholdValue;
   int upThresholdValue = lowThresholdValue*3;
   cvtColor(blurImage, grayBlurImage, CV_RGB2GRAY);
   //计?算?canny
   Canny(grayBlurImage, cannyImage, lowThresholdValue, upThresholdValue,sobelValue);

   contourImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   findContours(cannyImage, g_vContours, g_vHierarchy, RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
   for(int i = 0;i<g_vHierarchy.size();i++)
   {
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(contourImage, g_vContours, i, color,2,8,g_vHierarchy,0,Point(0,0));
   }
   vector<vector<Point>>hull(g_vContours.size());
   hullImage = Mat::zeros(cannyImage.rows, cannyImage.cols, CV_8UC3);
   for(int i = 0; i < g_vContours.size(); i++)
   {
       convexHull(Mat(g_vContours[i]), hull[i],false);
       Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255));
       drawContours(hullImage, hull, i, color,1,8,vector<Vec4i>(),0,Point(0,0));
   }
   
   imshow("hull image", hullImage);
   imshow("contour image", contourImage);
   imshow("canny Image",cannyImage);
}

三.对于轮廓代表的二维点集的其他处理方式

         获取点集的外围矩形边界

         API:Rect boundingRect(输入点集)

         返回点集的最小包围矩形

         API:RotatedRect minAreaRect(输入点集)

         寻找点集的最小包围圆心

         API:void minEnclosingCircle(输入点集,Point2f& 圆心,float& 半径)

         椭圆拟合二维点集

         API:RatatedRect fitEllipse(输入点集)

         逼近多边形曲线

         API:void approxPolyDp(输入二维点集,输出多边形逼近结果,double epsilon,bool close是否封闭)

         注:epsilon为原始曲线和近似曲线之间的最大值

            closed为真,则封闭,为假,得到的多边形不封闭

         以上各个API使用例程如下

//首º¡Á先¨¨查¨¦找¨°轮?廓¤a,再¨´用®?多¨¤边À?形?逼À?近¨¹轮?廓¤a
//然¨?后¨®依°¨¤靠?多¨¤边À?形?轮?廓¤a获?得Ì?包㨹围¡ì多¨¤边À?形?轮?廓¤a的Ì?圆2形? 最Á?小?矩?形? 矩?形?边À?界?
//需¨¨要°a变À?更¨¹的Ì?只?有®D二t值¦Ì化¡¥操¨´作Á¡Â时º¡À的Ì?阈D值¦Ì
//阈D值¦Ì最Á?大䨮值¦Ì 255 最Á?小?值¦Ì可¨¦变À?
Mat srcImage,srcCopyImage,srcGrayImage,srcThresholdImage,DstImage;
vector<vector<Point>>contours;
vector<Vec4i> hierarchys;
const int g_lowThresholdMax = 254;
int g_lowThresholdValue;
int g_upThresholdValue;
void onTrackBarLowThreshold(int pos,void* userData);
RNG g_rng(12345);
int main(int argc,char* argv[])
{
   srcImage = imread("F:\\opencv\\OpenCVImage\\contour.jpg");
   srcCopyImage = srcImage.clone();
   //转Áa化¡¥RGB为a灰¨°度¨¨图ª?像?
   if(srcImage.channels() == 3)
   {
       cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY);
   }
   else
   {
       srcGrayImage = srcImage.clone();
   }
   blur(srcGrayImage, srcGrayImage,Size(3,3));
   
   namedWindow("src image");
   namedWindow("threshold image");
   namedWindow("dst image");
   
   g_lowThresholdValue = 80;
   g_upThresholdValue = 255;
   createTrackbar("low threshold value", "threshold image", &g_lowThresholdValue, g_lowThresholdMax,onTrackBarLowThreshold,0);
   onTrackBarLowThreshold(g_lowThresholdValue, 0);
   
   imshow("src image", srcImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("threshold image", srcImage.cols, 0);
   moveWindow("dst image", srcImage.cols*2, 0);
   
   waitKey(0);
   return 0;
}


void onTrackBarLowThreshold(int pos,void* userData)
{
   if(g_lowThresholdValue == 0)g_lowThresholdValue = 1;
   threshold(srcGrayImage, srcThresholdImage, g_lowThresholdValue, g_upThresholdValue, THRESH_BINARY);
   //二t值¦Ì化¡¥完ª¨º成¨¦,寻¡ã找¨°轮?廓¤a
   findContours(srcThresholdImage, contours, hierarchys, RETR_TREE, CHAIN_APPROX_SIMPLE,Point(0,0));
   //多¨¤边À?形?毕À?竟1轮?廓¤a,先¨¨生¦¨²成¨¦变À?量¢?
   vector<vector<Point>>contours_polys(contours.size());//多¨¤边À?形?
   vector<Rect>boundRect(contours.size());//轮?廓¤a最Á?外ªa层?矩?形?边À?界?
   vector<Point2f>center(contours.size());//最Á?小?面?积y包㨹围¡ì圆2
   vector<float>radius(contours.size());
   
   DstImage = srcCopyImage.clone();
   for(int i = 0; i < contours.size();i++)
   {
       //逼À?近¨¹多¨¤边À?形?
       approxPolyDP(Mat(contours[i]), contours_polys[i], 3, true);//逼À?近¨¹精?度¨¨3且¨°封¤a闭À?
       //从䨮逼À?近¨¹到Ì?的Ì?多¨¤边À?形?得Ì?到Ì?最Á?外ªa层?矩?形?
       boundRect[i] = boundingRect(Mat(contours_polys[i]));
       //从䨮逼À?近¨¹的Ì?多¨¤边À?形?得Ì?到Ì?最Á?小?圆2形?
       minEnclosingCircle(Mat(contours_polys[i]), center[i], radius[i]);
   }
   //先¨¨绘?制?轮?廓¤a
   drawContours(DstImage, contours, -1, Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255)));
   //依°¨¤次ä?在¨²轮?廓¤a上¦?绘?制?矩?形?和¨ª圆2形?
   for(int i = 0; i < contours.size(); i++)
   {
       rectangle(DstImage, boundRect[i].tl(), boundRect[i].br(), Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255)),2,8,0);
       circle(DstImage, center[i], (int)radius[i], Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255),g_rng.uniform(0, 255)),2,8,0);
   }
   
   imshow("threshold image", srcThresholdImage);
   imshow("dst image", DstImage);
}

四.图像的矩

         图像说到底还是一个矩阵,而矩阵在不同的空间大小的情况下,进行分析的时候就需要用到矩阵的矩,也就是图像的矩,矩函数在图像分析中具有重要的作用,模式识别,目标分类,目标识别方位估计,图像编码重构等都需要用到图像的矩.

         图像的一阶矩与图像的形状相关,二阶矩显示图像中曲线围绕直线平均值的扩展程度,三阶矩是关于平均值的对称性的测量,由二阶矩和三阶矩可以到处七个不变矩,也即是7Hu不变矩.

         不变矩是图像的统计特征,满足平移,旋转,伸缩均不变的不变性,可以用于图像识别.

         另外,通过中心矩也可以计算图像的轮廓长度以及面积.

         API:Moments moments(源图像,bool 非0像素是否全看做1);

         注:源可以是二维数组或者单通道,八位或浮点型

                  第二个参数默认为false,非零像素不全部看作为1

         计算轮廓面积

         API:double contourArea(输入,bool 面向区域标识符)

         输入输入为向量或者是二维点,也可以说Mat,一般是查找到的图像的轮廓点集或者是根据轮廓点集拟合出的矩形,椭圆,圆,多边形,也可以使图像的凸包vector<poit2f>,返回图像的面积,默认为false,表示返回图像的面积是绝对值,不带符号.

         计算轮廓长度

         API:double arcLength(输入点集,bool 指示曲线是否封闭)

         注:输入点集类型与上一个api一致,默认曲线是封闭的.

测试使用图像的中心矩和opencv提供的算法,来计算图像轮廓面积,代码如下

//图ª?像?的Ì?矩?
//canny算?法¤¡§获?得Ì?二t值¦Ì图ª?像? 二t值¦Ì图ª?像?获?得Ì?边À?缘¦Ì 边À?缘¦Ì获?得Ì?边À?缘¦Ì矩? 边À?缘¦Ì矩?获?得Ì?中D心?距¨¤
//通ª¡§过y中D心?距¨¤计?算?图ª?像?的Ì?轮?廓¤a的Ì?面?积y和¨ª距¨¤离¤?

Mat srcImage,srcGrayImage,srcBlurImage,srcThresholdImage,srcCopyImage;

vector<vector<Point>>contours;
vector<Vec4i> hierarchys;

const int g_lowThresholdMax = 85;
int g_lowThresholdValue;
int g_upThresholdValue;
void onTrackBarLowThreshold(int pos,void* userData);

int main(int argc,char* argv[])
{
   srcImage = imread("F:\\opencv\\OpenCVImage\\mement.jpg");
   
   if(srcImage.channels() == 3)
   {
       cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY);
   }
   else
   {
       srcGrayImage = srcImage.clone();
   }
   
   blur(srcGrayImage,srcBlurImage,Size(3,3));
   
   namedWindow("canny image");
   g_lowThresholdValue = 80;
   g_upThresholdValue = g_lowThresholdValue*3;
   createTrackbar("low threshold value", "canny image", &g_lowThresholdValue, g_lowThresholdMax,onTrackBarLowThreshold,0);
   onTrackBarLowThreshold(g_lowThresholdValue, 0);
 
   imshow("src image", srcImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("canny image", srcBlurImage.cols, 0);
   moveWindow("dst image", srcBlurImage.cols*2, 0);
   
   waitKey(0);
   return 0;
}



void onTrackBarLowThreshold(int pos,void* userData)
{
   Canny(srcBlurImage, srcThresholdImage, g_lowThresholdValue, g_upThresholdValue,3);
   //二t进?制?图ª?像?获?取¨?轮?廓¤a
   findContours(srcThresholdImage, contours, hierarchys, RETR_TREE, CHAIN_APPROX_SIMPLE,Point(0,0));
   //计?算?矩?
   vector<Moments>mu(contours.size());
   for(int i = 0; i < contours.size(); i++)
   {
       mu[i] = moments(contours[i],false);
   }
   //计?算?中D心?矩?
   vector<Point2f>mc(contours.size());
   for(int i = 0; i < contours.size(); i++)
   {
       mc[i] = Point2f(static_cast<float>(mu[i].m10/mu[i].m00),static_cast<float>(mu[i].m01/mu[i].m00));
   }
   //绘?制?轮?廓¤a
   //srcCopyImage = srcImage.clone();
   srcCopyImage = Mat(srcImage.rows,srcImage.cols,CV_8UC1,Scalar::all(0));
   drawContours(srcCopyImage, contours, -1, Scalar(255));
   for(int i = 0; i < contours.size(); i++)
   {
       circle(srcCopyImage, mc[i], 4, Scalar(255),-1,8,0);
   }
   imshow("canny image", srcThresholdImage);
   imshow("dst image", srcCopyImage);
   
   //开a始º?计?算?轮?廓¤a并¡é且¨°输º?出?值¦Ì,通ª¡§过y矩?和¨ªopencv函¡¥数ºy计?算?出?来¤¡ä的Ì?面?积y对?比À¨¨
   for(int i = 0; i < contours.size();i++)
   {
       printf("计?算?轮?廓¤a面?积y以°?及¡ã长¡è度¨¨,第̨²%d个?轮?廓¤a的Ì?面?积y为a(矩?计?算?得Ì?出?):%.2f\n通ª¡§过yopencv函¡¥数ºy计?算?出?来¤¡ä的Ì?面?积y为a%.2f\t长¡è度¨¨为a%.2f\n",i,mu[i].m00,contourArea(contours[i],false),arcLength(contours[i],true));
   }
}

五.分水岭算法

         分水岭算法的主要意义在于分割图像,从背景图像中获得有用信息,比如在一张图像中,前景和背景的像素差异总是很大,此时需要将前景背景分离开来,就需要分水岭算法了.

         分水岭算法市一中基于标记的分割算法,表示的是输入图像的极大值点,在把图像传递给函数之前,需要大致勾画出图像中需要分割的区域,这些标记的值可以使用轮廓查找算法和轮廓绘制算法在图像中标记.

         最终形成的,是由极值点构成的一个一个的区域,如果图像中目标是连在一起的,分割起来有困难,可以使用该算法将黏着在一起的目标分开

         直接用边界来进行分水岭算法的效果不佳,一般来说,先对前后景进行标记,在应用分水岭算法,每个对象内部都是相连的,背景里面的每个像素都不属于任何目标,在应用分水岭算法就会取得较好的效果.

         void waterShed(输入图像,图像掩码);

         注:输入图像必须为八位三通道彩色图像,掩码是运算结果,32位单通道图像,和源图有一样的尺寸和类型,为啥为32位呢,因为一张图像完全可能被分成不止255个区域,那八位就不够用了呀.

         具体效果看如下代码例程

//分水岭算法waterShed

//在源图像中绘制区域线条,同时把绘制的区域线条保存在mask中,然后对mask进行

//轮廓查找算法,找到轮廓以后,在新的掩码中按照不同的轮廓绘制不同的灰度值

//调用分水岭算法,根据结果的不同灰度进行着色,最终的图像和源图像混合生成最终图像

//这就是分水岭算法的意义

//分水岭算法配合膨胀 腐蚀等形态学运算,效果应该很好

Mat srcImage,srcImageCopy;
Mat maskImage,maskImageCopy;
bool draw;
Point2i prevPoint;//记?录?前¡ã一°?个?鼠º¨®标À¨º事º?件t点Ì?的Ì?位?置?
RNG g_rng(12345);
void onMouseEvent(int event,int x,int y,int flag,void* userData);
int main(int argc,char* argv[])
{
   srcImage = imread("F:\\opencv\\OpenCVImage\\waterShed.jpg");
   imshow("src image", srcImage);
   prevPoint = Point2i(-1,-1);
   srcImageCopy = srcImage.clone();
   maskImage = Mat(srcImage.rows,srcImage.cols,CV_32SC1,Scalar::all(0));
   maskImageCopy = maskImage.clone();
   
   setMouseCallback("src image", onMouseEvent);
   imshow("dst image", maskImage);
   
   moveWindow("src image", 0, 0);
   moveWindow("dst image", srcImage.cols, 0);
   draw = false;
   
   int keyValue = 0;
   do
   {
       keyValue = waitKey(30);
       if(keyValue == '1')//清?除y图ª?像?
       {
           draw = false;
           srcImageCopy = srcImage.clone();
           maskImageCopy = maskImage.clone();
           prevPoint = Point2i(-1,-1);
           imshow("src image", srcImageCopy);
           imshow("dst image", maskImageCopy);
       }
       else if(keyValue == '2')
       {
           //开a始º?计?算?
           if(draw == true)//有®D轮?廓¤a
           {
               vector<vector<Point2i>>contours;
               vector<Vec4i>hierarchy;
               findContours(maskImageCopy, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
               //检¨¬查¨¦轮?廓¤a
               if(contours.size() == 0)continue;
               //找¨°出?轮?廓¤a以°?后¨®将?轮?廓¤a绘?制?在¨²源¡ämask上¦?
               //对?不?同ª?的Ì?轮?廓¤a,用®?不?同ª?的Ì?值¦Ì进?行D区?分¤?
               Mat maskDstImage = Mat(maskImageCopy.size(),CV_32SC1,Scalar::all(0));
               int c_comp = 0;
               for(int i = 0 ; i >= 0;i = hierarchy[i][0],c_comp++)
               {
                   //hierarchy[i][0]对?应®|后¨®一°?个?轮?廓¤a
                   //绘?制?轮?廓¤a,轮?廓¤a的Ì?值¦Ì依°¨¤次ä?为a1 2 3 4 5
                   drawContours(maskDstImage, contours, i, Scalar::all(c_comp+1),-1,8,hierarchy,INT_MAX);
               }
               //此ä?时º¡À,一°?共2有®Dc_comp个?轮?廓¤a点Ì?,生¦¨²成¨¦scalar数ºy组Á¨¦,用®?于®¨²后¨®期¨²着Á?色¦?
               vector<Vec3b>colorTab;
               for (int i = 0; i < c_comp; i++) {
                   int r = g_rng.uniform(0, 255);
                   int g = g_rng.uniform(0, 255);
                   int b = g_rng.uniform(0, 255);
                   colorTab.push_back(Vec3b((uchar)r,(uchar)g,(uchar)b));
               }
               //进?行D分¤?水?岭¢?处ä|理¤¨ª算?法¤¡§
               double startTime = (double)getTickCount();
               watershed(srcImageCopy, maskDstImage);
               double endTime = (double)getTickCount();
               printf("算?法¤¡§使º1用®?时º¡À间?为a%.2f \r\n",((endTime-startTime)*1000)/getTickFrequency());
               //得Ì?到Ì?了¢?分¤?水?岭¢?图ª?像?以°?后¨®,按ã¡ä照?colortab的Ì?内¨²容¨Y,对?分¤?水?岭¢?图ª?像?进?行D区?域®¨°着Á?色¦?
               //分¤?水?岭¢?算?法¤¡§的Ì?处ä|理¤¨ª结¨¢果?存ä?放¤?在¨²maskdstImage中D
               Mat dstImage(maskDstImage.size(),CV_8UC3);
               for(int i = 0 ; i< maskDstImage.rows;i++)
               {
                   for (int j = 0; j < maskDstImage.cols; j++) {
                       int index = maskDstImage.at<int>(i,j);
                       if(index == -1)
                       {
                           dstImage.at<Vec3b>(i,j) = Vec3b(255,255,255);
                       }
                       else if(index < 0 || index > c_comp)
                       {
                           dstImage.at<Vec3b>(i,j) = Vec3b(0,0,0);
                       }
                       else
                       {
                           dstImage.at<Vec3b>(i,j) = colorTab[index-1];
                       }
                   }
               }
               //再¨´把ã?源¡ä图ª?像?和¨ª得Ì?到Ì?的Ì?掩¨²码?图ª?像?混¨¬合?显?示º?新?的Ì?图ª?片?
               addWeighted(srcImage, 0.5, dstImage, 0.5, 0.0, dstImage);
               //这a里¤?就¨ª已°?经-得Ì?到Ì?最Á?终?的Ì?maskdst,显?示º?看¡ä看¡ä
               imshow("dst image", dstImage);
           }
           else
           {
               continue;
           }
       }
       
   }while(keyValue != 27);
   
   return 0;
}

void onMouseEvent(int event,int x,int y,int flag,void* userData)
{
   if(x < 0||y< 0||x>=srcImage.cols||y>=srcImage.rows)
       return;
   if(event == EVENT_LBUTTONDOWN)
   {
       prevPoint = Point2i(x,y);
   }
   else if(event == EVENT_LBUTTONUP)
   {
       prevPoint = Point2i(-1,-1);
   }
   else if(event == EVENT_MOUSEMOVE)
   {
       if(flag&EVENT_FLAG_LBUTTON)
       {
           //鼠º¨®标À¨º左Á¨®键¨¹滑?动¡¥
           draw = true;
           line(srcImageCopy, prevPoint,Point2i(x,y), Scalar::all(255),4,LINE_AA);
           line(maskImageCopy,prevPoint,Point2i(x,y), Scalar::all(INT_MAX),4,LINE_AA);
           prevPoint = Point2i(x,y);
           imshow("dst image", maskImageCopy);
           imshow("src image", srcImageCopy);
       }
   }
}

六.图像修复

        图像修复是指利用那些已经被破坏区域的边沿,即边缘的颜色和结构,通过对它的繁殖和混合,填充的被损坏区域中去,已达到图像修补的目的,注意,边缘损坏过多的图像也是难以修复的,对于小块破损比较有效

         API:void inPaint(原图片,修复区域掩码,输出图片,double inpaintRadius修复算法参考半径,int 修复算法标识符)

         注:1.原图片必须为八位单通道或者是三通道图片,掩码是八位单通道图片,其中那个非零的那些像素点表示需要修复的区域,输出图像必须和源图像有一样的尺寸和类型,最小修复半径修复的每个店所参考的周围区域颜色的半径.

                  2.修复算法标识符表示用什么修复算法进行计算,取值INPAINT_NS基于nerier_stokes方法 ALEXANDRU_TELEA另一种方法.

         修复图片的例子见面的代码

//图ª?像?修T补1
//需¨¨要°a原-图ª? 掩¨²码?图ª? 目?标À¨º图ª?
//修T复¡ä算?法¤¡§参?考?半ã?径?为a3 修T复¡ä方¤?法¤¡§为a INPAINT_TELEA

Mat srcImage,srcImageCopy;
Mat maskImage,maskImageCopy;
bool draw;

Point2i prevPoint;//记?录?前¡ã一°?个?鼠º¨®标À¨º事º?件t点Ì?的Ì?位?置?
void onMouseEvent(int event,int x,int y,int flag,void* userData);


int main(int argc,char* argv[])
{
    srcImage = imread("F:\\opencv\\OpenCVImage\\inpaint.jpg");
    imshow("src image", srcImage);
    prevPoint = Point2i(-1,-1);
    srcImageCopy = srcImage.clone();
    maskImage = Mat(srcImage.rows,srcImage.cols,CV_8UC1,Scalar::all(0));
    maskImageCopy = maskImage.clone();

    setMouseCallback("src image", onMouseEvent);
    imshow("dst image", maskImage);

    moveWindow("src image", 0, 0);
    moveWindow("dst image", srcImage.cols, 0);
    draw = false;



    int keyValue = 0;
    do {
        keyValue = waitKey(30);
        if(keyValue == '1')
        {
            //清?除y
            draw = false;
            srcImageCopy = srcImage.clone();
            maskImageCopy = maskImage.clone();
            prevPoint = Point2i(-1,-1);
            imshow("src image", srcImageCopy);
            imshow("dst image", maskImageCopy);
        }
        else if(keyValue == '2')
        {
            //修T复¡ä
            if(draw == true)
            {
                Mat srcImageInpaint = srcImageCopy.clone();
                Mat dstImage = Mat(srcImageInpaint.size(),CV_8UC3,Scalar::all(0));
                inpaint(srcImageInpaint, maskImageCopy, dstImage, 3, INPAINT_TELEA);
                srcImageCopy = srcImage.clone();
                maskImageCopy = maskImage.clone();
                prevPoint = Point2i(-1,-1);
                imshow("dst image", dstImage);
            }
        }
    } while (keyValue != 27);
    return 0;
}

void onMouseEvent(int event,int x,int y,int flag,void* userData)
{
    if(x < 0||y< 0||x>=srcImage.cols||y>=srcImage.rows)
        return;
    if(event == EVENT_LBUTTONDOWN)
    {
        prevPoint = Point2i(x,y);
    }
    else if(event == EVENT_LBUTTONUP)
    {
        prevPoint = Point2i(-1,-1);
    }
    else if(event == EVENT_MOUSEMOVE)
    {
        if(flag&EVENT_FLAG_LBUTTON)
        {
            //鼠º¨®标À¨º左Á¨®键¨¹滑?动¡¥
            draw = true;
            line(srcImageCopy, prevPoint,Point2i(x,y), Scalar::all(255),4,LINE_AA);
            line(maskImageCopy,prevPoint,Point2i(x,y), Scalar::all(INT_MAX),4,LINE_AA);
            prevPoint = Point2i(x,y);
            imshow("dst image", maskImageCopy);
            imshow("src image", srcImageCopy);
        }
    }
}

 

转载于:https://www.cnblogs.com/dengxiaojun/p/5252294.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: OpenCV提供了一个函数cv::findContours(),可以用于从二值图像中提取轮廓。 具体步骤如下: 1. 将彩色图像转化为灰度图像。 2. 对灰度图像进行二值化处理,得到二值图像。 3. 使用cv::findContours()函数提取轮廓。 4. 绘制轮廓。 下面是一个示例代码: ```python import cv2 import numpy as np # 读入图像 img = cv2.imread('image.jpg') # 转化为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化处理 ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 提取轮廓 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 cv2.drawContours(img, contours, -1, (0, 0, 255), 2) # 显示图像 cv2.imshow("image", img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 其中,cv2.findContours()函数的参数说明如下: - 第一个参数:二值图像。 - 第二个参数:轮廓检索模式。有以下四种模式: - cv2.RETR_EXTERNAL:只检测外轮廓。 - cv2.RETR_LIST:检测所有轮廓,不建立轮廓之间的层级关系。 - cv2.RETR_CCOMP:检测所有轮廓,并将轮廓分为两级,即外层轮廓和内层轮廓。 - cv2.RETR_TREE:检测所有轮廓,并建立轮廓之间的层级关系。 - 第三个参数:轮廓逼近方法。有以下三种方法: - cv2.CHAIN_APPROX_NONE:存储所有的轮廓点。 - cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直、对角线方向上的像素点,只保留端点。 - cv2.CHAIN_APPROX_TC89_L1、cv2.CHAIN_APPROX_TC89_KCOS:使用Teh-Chin链逼近算法。 返回值: - contours:检测到的轮廓,每个轮廓是一个Numpy数组。 - hierarchy:检测到的轮廓之间的层级关系,每个轮廓有四个值:[next, previous, child, parent]。其中,next表示下一个轮廓的索引,previous表示前一个轮廓的索引,child表示第一个子轮廓的索引,parent表示父轮廓的索引。如果当前轮廓没有子轮廓,则child=-1;如果当前轮廓没有父轮廓,则parent=-1。 ### 回答2: 在OpenCV中,图像轮廓提取是一种常用的图像处理技术,它可以帮助我们找到图像中对象的边界。轮廓提取可以用于许多应用,如形状识别、物体检测和图像分割等。 在OpenCV中,图像轮廓提取的主要步骤如下: 1. 将图像转换为灰度图像,这有助于减少噪声并简化处理。 2. 对图像进行阈值处理,将图像转换为二值图像。这样可以将对象与背景分离。 3. 对二值图像进行形态学操作,如腐蚀和膨胀,可以去除噪声并平滑边界。 4. 使用cv2.findContours()函数找到图像中的轮廓。该函数将返回一个包含所有轮廓的列表。 5. 可选地,可以使用cv2.drawContours()函数在原始图像上绘制轮廓。这可以帮助我们可视化轮廓提取的结果。 在进行图像轮廓提取时,还可以使用一些参数来调整轮廓提取的效果。例如,可以通过调整阈值值和形态学操作来控制轮廓的数量和精度。 总的来说,OpenCV提供了简单且强大的图像轮廓提取工具,它可以帮助我们提取图像中的对象边界,进而实现各种应用。学习和掌握轮廓提取技术对于图像处理和计算机视觉相关领域的研究和应用非常重要。 ### 回答3: OpenCV是一个强大的计算机视觉库,具有丰富的图像处理功能,其中包括图像轮廓提取。 图像轮廓是指图像中连接相同颜色或强度的连续曲线的图像特征。在OpenCV中,图像轮廓提取是通过以下几个步骤实现的: 首先,我们需要将图像转换为灰度图像。这是因为灰度图像只有一个通道,更容易处理。可以使用cvtColor()函数将彩色图像转换为灰度图像。 然后,我们可以使用阈值函数将图像二值化。通过设定适当的阈值,我们可以将图像分为目标和背景两部分。可以使用threshold()函数设置阈值,并根据需要选择不同的阈值类型。 接下来,使用findContours()函数可以检测图像中的轮廓。该函数将按大小排序返回一系列轮廓,并存储为一系列点的集合。可以选择提取所有的轮廓,或者只提取特定大小的轮廓。还可以使用其他参数来调整轮廓的检测精度和准确性。 提取轮廓后,可以使用drawContours()函数将轮廓绘制在原始图像上。该函数可以选择绘制所有的轮廓或单独绘制其中的一个轮廓。 如果需要进一步处理轮廓,可以使用一些附加函数,如计算轮廓的面积、周长、边界框等。还可以对轮廓进行操作,例如填充、裁剪或平滑处理。 最后,通过使用imshow()函数可以将处理后的图像显示出来。 总之,OpenCV提供了一套完整且易于使用的函数,可以方便地从图像中提取轮廓。通过适当的处理和调整参数,我们可以根据实际需求获取准确的轮廓信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值