图像处理 找椭圆_椭圆检测 图像处理 - CSDN
  • 它是是图像处理中从图像中识别几何形状的基本方法之一,其本质是将直角坐标系映射到极坐标系。 直线可以表示为y=mx+c,或者以极坐标形式表示为r=xcosθ+ysinθ,其中r是原点到直线的垂直距离,θ是水平轴顺时针方向...

    关于霍夫变化,网上有多理论介绍,随便找一篇https://www.cnblogs.com/AndyJee/p/3805594.html
    它是是图像处理中从图像中识别几何形状的基本方法之一,其本质是将直角坐标系映射到极坐标系。

    直线可以表示为y=mx+c,或者以极坐标形式表示为r=xcosθ+ysinθ,其中r是原点到直线的垂直距离,θ是水平轴顺时针方向到垂直线的夹角(这个方向取决于坐标的形式,在OpenCV就是采用这种极坐标形式)。
    

    在这里插入图片描述
    最基本的霍夫变换是从黑白图像中检测直线,同时也可以用来检测图像中简单的结构。
    要想自己实现霍夫变化,可以参考https://blog.csdn.net/ccxcau/article/details/7816588。
    大致思路如下:

    1. 读取图像
    2. 获取图像空间的源像素数据
    3. 通过量化霍夫参数空间为有限个值间隔等分或者累加格子,即p,theta
    4. 霍夫变换算法开始,每个像素坐标点P(x, y)被转换到(r, theta)的曲线点上面,并累加到对应的格子数据点
    5. 寻找最大霍夫值,设置阈值,反变换到图像空间;

    我只是想学习opencv自带的霍夫变化函数而已,不用研究那么复杂。

    检测直线

    OpenCV中提供了函数HoughLines 和 HoughLinesP来检测直线。
    官网说明参见https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html

    • 标准霍夫线变换:HoughLines,它能给我们提供一组参数对(ρ,θ)的集合来表示检测到的直线
    HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]]) -> lines
    

    参数说明

    1. image:表示边缘检测的输出图像,该图像为单通道8位二进制图像。进行霍夫变换之前要进行二值化(由非0像素表示)或者canny边缘检测。
    2. rho:表示参数极径 r 以像素值为单位的分辨率。
    3. theta:表示参数极角 \theta 以弧度为单位的分辨率。
    4. threshold:表示检测一条直线所需最少的曲线交点。只有当累加器中的值高于threshold时才被当成是直线
    5. lines:表示储存着检测到的直线的参数对 (x_{start}, y_{start}, x_{end}, y_{end}) 的容器,也就是线段两个端点的坐标。
    6. minLineLength:表示能组成一条直线的最少点的数量,点数量不足的直线将被抛弃。
    7. maxLineGap:表示能被认为在一条直线上的亮点的最大距离。

    直接使用官网的例子来测试一下,图片是在百度中搜索获得的。

    import cv2
    import numpy as np
    
    img = cv2.imread('hough_test1.jpg')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray,50,150,apertureSize = 3)
    
    lines = cv2.HoughLines(edges,1,np.pi/180,200) # rho=1,theta=np.pi/180
    for rho,theta in lines[0]:
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
    
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
    
    cv2.imshow('Result', img)
    cv2.waitKey(0)
    cv2.imwrite('houghlines3.jpg',img)
    
    

    执行一下
    在这里插入图片描述

    左边原图,右边执行效果,不过只有一根线。

    观察lines.shape,可以了解它是一个三维数组。其中10表示代表检测到10条直线。

    print("lines.shape: ", lines.shape)
    print("lines: ", lines)
    结果:
    ('lines.shape: ', (10L, 1L, 2L))
    ('lines: ', array([[[302.       ,   1.4660766]],
    
           [[268.       ,   1.3613569]],
    
           [[282.       ,   1.4137167]],
    
           [[276.       ,   1.3962634]],
    
           [[327.       ,   1.5707964]],
    
           [[319.       ,   1.5358897]],
    
           [[287.       ,   1.5184364]],
    
           [[325.       ,   1.5707964]],
    
           [[328.       ,   1.553343 ]],
    
           [[330.       ,   1.553343 ]]], dtype=float32))
    

    进一步打印一下lines[0]

    print("lines[0]:", lines[0])
    结果:
    ('lines[0]:', array([[302.       ,   1.4660766]], dtype=float32))
    

    这是三维数组中的第一条数据,它返回(ρ,θ),ρ的单位是像素,θ是弧度,都是float类型。根据这2个数,就可以画出一根检测出的直线。

    如果要想把检测出的10根直线都画出来,可以采用以下方法

    # -*- coding: cp936 -*-
    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    img = cv2.imread('hough_test1.jpg')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray,50,150,apertureSize = 3)
    
    lines = cv2.HoughLines(edges,1,np.pi/180,200)  # rho=1,theta=np.pi/180
    #print("lines.shape: ", lines.shape)
    #print("lines: ", lines)
    #print("lines[0]:", lines[0])
    for line in lines:
        rho = line[0][0]  # 第一个元素是距离rho
        theta = line[0][1]  # 第二个元素是角度theta
        
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
    
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
    
    cv2.imshow('Result', img)
    cv2.waitKey(0)
    cv2.imwrite('houghlines3.jpg',img)
    
    

    在这里插入图片描述
    上面结果可以发现检测到的线都画上了。

    至于为什么要乘以1000 画出所有直线,可以参考https://blog.csdn.net/yl_best/article/details/88744997。

    通过上面的例子可以看出,其中霍夫变换看起来就像在图像中查找对齐的边界像素点集合。
    但这样会在一些情况下导致虚假检测,如像素偶然对齐或多条直线穿过同样的对齐像素造成的多重检测。

    要避免这样的问题,并检测图像中分段的直线(而不是贯穿整个图像的直线),由此出现了概率霍夫变换(Probabilistic Hough)

    • 统计概率霍夫线变换:HoughLinesP,这是执行起来效率更高的霍夫线变换。它输出检测到的直线的端点 (x0, y0, x1, y1)

    根据定义可以发现,HoughLines检测结果是直线,而HoughLinesP检测结果是线段。

    HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None) 
    

    继续用官网改进版代码测试一下

    # -*- coding: cp936 -*-
    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    
    img = cv2.imread('hough_test1.jpg')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray,50,150,apertureSize = 3)
    minLineLength = 100
    maxLineGap = 10
    lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
    
    print("lines.shape: ", lines.shape)
    print("lines[0]:", lines[0])
    
    for line in lines:
        x1 = line[0][0]
        y1 = line[0][1]
        x2 = line[0][2]
        y2 = line[0][3]
        cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
    
    cv2.imshow('Result', img)
    cv2.waitKey(0)
    cv2.imwrite('houghlines3.jpg',img)
    

    在这里插入图片描述
    结果如上图。

    检测圆

    OpenCV中提供了函数HoughCircles来检测圆。
    官网说明参见https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html

    HoughCircles(image,method,dp,minDist[, circles[,param1, param2[,minRadius[,maxRadius]]]]])
    

    其返回N个圆的信息储存在1×N×3的ndarray。核心内容就是:(x,y,半径),分别是圆心的行,列,半径,这是一个圆的基本信息。

    • image
    • method cv2.HOUGH_GRADIENT 也就是霍夫圆检测,梯度法
    • dp 计数器的分辨率图像像素分辨率与参数空间分辨率的比值(官方文档上写的是图像分辨率与累加器分辨率的比值,它把参数空间认为是一个累加器,毕竟里面存储的都是经过的像素点的数量),dp=1,则参数空间与图像像素空间(分辨率)一样大,dp=2,参数空间的分辨率只有像素空间的一半大
    • minDist 圆心之间最小距离,如果距离太小,会产生很多相交的圆,如果距离太大,则会漏掉正确的圆
    • param1 canny检测的双阈值中的高阈值,低阈值是它的一半
    • param2 最小投票数(基于圆心的投票数)
    • minRadius 需要检测院的最小半径
    • maxRadius 需要检测院的最大半径

    使用官网代码测试一下

    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    
    img = cv2.imread('hough_test2.jpg',0)
    img = cv2.medianBlur(img,5)
    cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
    
    circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,
                                param1=50,param2=30,minRadius=0,maxRadius=0)
    
    circles = np.uint16(np.around(circles))
    print("circles.shape: ", circles.shape)
    for i in circles[0,:]:
        # draw the outer circle
        cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
        # draw the center of the circle
        cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
    
    cv2.imshow('Result', cimg)
    cv2.waitKey(0)
    cv2.imwrite('houghlines3.jpg',cimg)
    

    在这里插入图片描述
    结果如上,绿色是检测到的圆形,红色是圆心。

    检测椭圆

    在OpenCV中我没有找到椭圆检测函数。
    不过另外一个库skimage中提供了函数hough_ellipse来检测圆。
    skimage,也就是Scikit-Image,它是基于scipy的一款图像处理包。说明参见https://blog.csdn.net/qq_41185868/article/details/80330262

    安装库如下,需要2个,Scikit-Image和scipy

    C:\Users\xxx\files\car>pip install Scikit-Image
    Collecting Scikit-Image
      Downloading https://files.pythonhosted.org/packages/12/83/a2e4d88003ecde0c008c1c1dc2d3054bd1d9940500d2fbdaf6304894ee38/scikit_image-0.14.3-cp27-none-win_amd64.whl (24.7MB)
        100% |████████████████████████████████| 24.7MB 6.8kB/s
    Requirement already satisfied: six>=1.10.0 in c:\python27\lib\site-packages (from Scikit-Image)
    Collecting cloudpickle>=0.2.1 (from Scikit-Image)
      Downloading https://files.pythonhosted.org/packages/09/f4/4a080c349c1680a2086196fcf0286a65931708156f39568ed7051e42ff6a/cloudpickle-1.2.1-py2.py3-none-any.whl
    Collecting PyWavelets>=0.4.0 (from Scikit-Image)
      Downloading https://files.pythonhosted.org/packages/1f/da/9c63070111c44fd8ee1c310efffcd130e062bfcbeaa3effc2e6ae8072386/PyWavelets-1.0.3-cp27-none-win_amd64.whl (4.2MB)
        100% |████████████████████████████████| 4.2MB 10kB/s
    Collecting networkx>=1.8 (from Scikit-Image)
      Downloading https://files.pythonhosted.org/packages/f3/f4/7e20ef40b118478191cec0b58c3192f822cace858c19505c7670961b76b2/networkx-2.2.zip (1.7MB)
        100% |████████████████████████████████| 1.7MB 25kB/s
    Requirement already satisfied: pillow>=4.3.0 in c:\python27\lib\site-packages (from Scikit-Image)
    Requirement already satisfied: numpy>=1.9.1 in c:\python27\lib\site-packages (from PyWavelets>=0.4.0->Scikit-Image)
    Collecting decorator>=4.3.0 (from networkx>=1.8->Scikit-Image)
      Downloading https://files.pythonhosted.org/packages/5f/88/0075e461560a1e750a0dcbf77f1d9de775028c37a19a346a6c565a257399/decorator-4.4.0-py2.py3-none-any.whl
    Installing collected packages: cloudpickle, PyWavelets, decorator, networkx, Scikit-Image
      Running setup.py install for networkx ... done
    Successfully installed PyWavelets-1.0.3 Scikit-Image-0.14.3 cloudpickle-1.2.1 decorator-4.4.0 networkx-2.2
    You are using pip version 9.0.3, however version 19.1.1 is available.
    You should consider upgrading via the 'python -m pip install --upgrade pip' command.
    
    C:\Users\xxx\files\car>pip install scipy
    Collecting scipy
      Downloading https://files.pythonhosted.org/packages/49/73/22e125f335986fdc3b03c09cdf8ffe0d9d5471eec301aeb2e33db78b3e7f/scipy-1.2.2-cp27-cp27m-win_amd64.whl (30.5MB)
        100% |████████████████████████████████| 30.5MB 29kB/s
    Requirement already satisfied: numpy>=1.8.2 in c:\python27\lib\site-packages (from scipy)
    Installing collected packages: scipy
    Successfully installed scipy-1.2.2
    You are using pip version 9.0.3, however version 19.1.1 is available.
    You should consider upgrading via the 'python -m pip install --upgrade pip' command.
    

    hough_ellipse函数如下

    hough_ellipse(img,accuracy, threshold, min_size, max_size)
    

    参数说明

    • img: 待检测图像
    • accuracy: 使用在累加器上的短轴二进制尺寸,是一个double型的值,默认为1
    • thresh: 累加器阈值,默认为4
    • min_size: 长轴最小长度,默认为4
    • max_size: 短轴最大长度,默认为None,表示图片最短边的一半

    返回一个 [(accumulator, y0, x0, a, b, orientation)] 数组

    • accumulator表示累加器
    • (y0,x0)表示椭圆中心点
    • (a,b)分别表示长短轴
    • orientation表示椭圆方向

    找个例子,直接测试

    # -*- coding: cp936 -*-
    import matplotlib.pyplot as plt
    from skimage import data,draw,color,transform,feature,io,img_as_uint
    
    
    #加载图片,转换成灰度图并检测边缘
    image_rgb = data.coffee()[0:220, 160:420] #裁剪原图像,不然速度非常慢
    image_gray = color.rgb2gray(image_rgb)
    '''
    print(image_rgb.dtype.name) #uint8
    print(image_gray.dtype.name) #float64 
    print image_gray.shape #(220L, 260L)
    #io.imshow(image_gray)
    '''
    edges = feature.canny(image_gray, sigma=2.0, low_threshold=0.55, high_threshold=0.8)
    #print(edges.dtype.name) #bool
    
    #执行椭圆变换
    result =transform.hough_ellipse(edges, accuracy=20, threshold=250,min_size=100, max_size=120)
    result.sort(order='accumulator') #根据累加器排序
    
    #估计椭圆参数
    best = list(result[-1])  #排完序后取最后一个
    yc, xc, a, b = [int(round(x)) for x in best[1:5]]
    orientation = best[5]
    
    #在原图上画出椭圆
    cy, cx =draw.ellipse_perimeter(yc, xc, a, b, orientation)
    image_rgb[cy, cx] = (0, 0, 255) #在原图中用蓝色表示检测出的椭圆
    #print image_rgb
    #io.imshow(image_rgb)
    
    
    #分别用白色表示canny边缘,用红色表示检测出的椭圆,进行对比
    #print edges.shape #(220L, 260L)
    edges = color.gray2rgb(edges)
    #print(edges.dtype.name) #bool
    
    edges = img_as_uint(edges)  #转化类型
    #io.imshow(edges)
    #print edges.shape #(220L, 260L, 3L)
    
    edges[cy, cx] = (250, 0, 0) 
    #io.imshow(edges)
    
    fig2, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, figsize=(8, 4))
    
    ax1.set_title('Original picture')
    ax1.imshow(image_rgb)
    
    ax2.set_title('Edge (white) and result (red)')
    ax2.imshow(edges)
    
    plt.show()
    
    

    在这里插入图片描述

    结果如上图。左边是原图,蓝色线画了椭圆霍夫变化结果。右边是白色边缘检测结果,红色是霍夫变化结果。

    椭圆检测过程说明在代码中都有,很详细。
    简单说明一下,把别人代码拿过来执行时候报错如下

    ValueError: 3-dimensional arrays must be of dtype unsigned byte, unsigned short, float32 or float64
    

    把edges内容打印一下,结果显示满屏都是False。查了网上,发现边缘检测canny的结果是二进制图。
    所以,我加了一句代码,利用skimage自带类型转化edges = img_as_uint(edges) ,把edges转化为int型,然后就成功了。

    展开全文
  • 图像中寻找轮廓 目标 在这个教程中你将学到如何: 使用OpenCV函数 findContours使用OpenCV函数 drawContours 原理 例程 教程的代码在下面给出. 你也可以从 这里 下载 #...

    在图像中寻找轮廓

    目标

    在这个教程中你将学到如何:

    原理

    例程

    教程的代码在下面给出. 你也可以从 这里 下载

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    using namespace std;
    
    Mat src; Mat src_gray;
    int thresh = 100;
    int max_thresh = 255;
    RNG rng(12345);
    
    /// Function header
    void thresh_callback(int, void* );
    
    /** @function main */
    int main( int argc, char** argv )
    {
      /// 加载源图像
      src = imread( argv[1], 1 );
    
      /// 转成灰度并模糊化降噪
      cvtColor( src, src_gray, CV_BGR2GRAY );
      blur( src_gray, src_gray, Size(3,3) );
    
      /// 创建窗体
      char* source_window = "Source";
      namedWindow( source_window, CV_WINDOW_AUTOSIZE );
      imshow( source_window, src );
    
      createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
      thresh_callback( 0, 0 );
    
      waitKey(0);
      return(0);
    }
    
    /** @function thresh_callback */
    void thresh_callback(int, void* )
    {
      Mat canny_output;
      vector<vector<Point> > contours;
      vector<Vec4i> hierarchy;
    
      /// 用Canny算子检测边缘
      Canny( src_gray, canny_output, thresh, thresh*2, 3 );
      /// 寻找轮廓
      findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
    
      /// 绘出轮廓
      Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
      for( int i = 0; i< contours.size(); i++ )
         {
           Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
           drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
         }
    
      /// 在窗体中显示结果
      namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
      imshow( "Contours", drawing );
    }
    

    例程说明

    结果

    1. 原图和检测到的轮廓如下:

      contour_0

      contour_1



    计算物体的凸包

    目标

    在这个教程中你将学习到如何:

    原理

    例程

    教程的代码在下面给出. 你也可以从 这里 下载

     #include "opencv2/highgui/highgui.hpp"
     #include "opencv2/imgproc/imgproc.hpp"
     #include <iostream>
     #include <stdio.h>
     #include <stdlib.h>
    
     using namespace cv;
     using namespace std;
    
     Mat src; Mat src_gray;
     int thresh = 100;
     int max_thresh = 255;
     RNG rng(12345);
    
     /// Function header
     void thresh_callback(int, void* );
    
    /** @function main */
    int main( int argc, char** argv )
     {
       /// 加载源图像
       src = imread( argv[1], 1 );
    
       /// 转成灰度图并进行模糊降噪
       cvtColor( src, src_gray, CV_BGR2GRAY );
       blur( src_gray, src_gray, Size(3,3) );
    
       /// 创建窗体
       char* source_window = "Source";
       namedWindow( source_window, CV_WINDOW_AUTOSIZE );
       imshow( source_window, src );
    
       createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
       thresh_callback( 0, 0 );
    
       waitKey(0);
       return(0);
     }
    
     /** @function thresh_callback */
     void thresh_callback(int, void* )
     {
       Mat src_copy = src.clone();
       Mat threshold_output;
       vector<vector<Point> > contours;
       vector<Vec4i> hierarchy;
    
       /// 对图像进行二值化
       threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
    
       /// 寻找轮廓
       findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
    
       /// 对每个轮廓计算其凸包
       vector<vector<Point> >hull( contours.size() );
       for( int i = 0; i < contours.size(); i++ )
          {  convexHull( Mat(contours[i]), hull[i], false ); }
    
       /// 绘出轮廓及其凸包
       Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
       for( int i = 0; i< contours.size(); i++ )
          {
            Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
            drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
            drawContours( drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
          }
    
       /// 把结果显示在窗体
       namedWindow( "Hull demo", CV_WINDOW_AUTOSIZE );
       imshow( "Hull demo", drawing );
     }
    

    例程说明

    结果

    1. 原图和结果图如下:

      Hull_0

      Hull_1







    创建包围轮廓的矩形和圆形边界框

    目标

    在这节教程中您将学到:

    • 使用OpenCV函数 boundingRect 来计算包围轮廓的矩形框.
    • 使用OpenCV函数 minEnclosingCircle 来计算完全包围已有轮廓最小圆.

    原理

    代码

    下面是本节教程源码. 你也可以从 这里 下载.

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    using namespace std;
    
    Mat src; Mat src_gray;
    int thresh = 100;
    int max_thresh = 255;
    RNG rng(12345);
    
    /// 函数声明
    void thresh_callback(int, void* );
    
    /** @主函数 */
    int main( int argc, char** argv )
    {
      /// 载入原图像, 返回3通道图像
      src = imread( argv[1], 1 );
    
      /// 转化成灰度图像并进行平滑
      cvtColor( src, src_gray, CV_BGR2GRAY );
      blur( src_gray, src_gray, Size(3,3) );
    
      /// 创建窗口
      char* source_window = "Source";
      namedWindow( source_window, CV_WINDOW_AUTOSIZE );
      imshow( source_window, src );
    
      createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
      thresh_callback( 0, 0 );
    
      waitKey(0);
      return(0);
    }
    
    /** @thresh_callback 函数 */
    void thresh_callback(int, void* )
    {
      Mat threshold_output;
      vector<vector<Point> > contours;
      vector<Vec4i> hierarchy;
    
      /// 使用Threshold检测边缘
      threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
      /// 找到轮廓
      findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
    
      /// 多边形逼近轮廓 + 获取矩形和圆形边界框
      vector<vector<Point> > contours_poly( contours.size() );
      vector<Rect> boundRect( contours.size() );
      vector<Point2f>center( contours.size() );
      vector<float>radius( contours.size() );
    
      for( int i = 0; i < contours.size(); i++ )
         { approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
           boundRect[i] = boundingRect( Mat(contours_poly[i]) );
           minEnclosingCircle( contours_poly[i], center[i], radius[i] );
         }
    
    
      /// 画多边形轮廓 + 包围的矩形框 + 圆形框
      Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
      for( int i = 0; i< contours.size(); i++ )
         {
           Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
           drawContours( drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
           rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );
           circle( drawing, center[i], (int)radius[i], color, 2, 8, 0 );
         }
    
      /// 显示在一个窗口
      namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
      imshow( "Contours", drawing );
    }
    

    代码说明

    结果

    1. 如下所示:

      BRC_0

      BRC_1






    为轮廓创建可倾斜的边界框和椭圆

    目标

    在这个教程中你将学习到如何:

    原理

    例程

    例程的代码在下面显示. 你也可以从 这里 下载

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    using namespace std;
    
    Mat src; Mat src_gray;
    int thresh = 100;
    int max_thresh = 255;
    RNG rng(12345);
    
    /// Function header
    void thresh_callback(int, void* );
    
    /** @function main */
    int main( int argc, char** argv )
    {
      /// 加载源图像
      src = imread( argv[1], 1 );
    
      /// 转为灰度图并模糊化
      cvtColor( src, src_gray, CV_BGR2GRAY );
      blur( src_gray, src_gray, Size(3,3) );
    
      /// 创建窗体
      char* source_window = "Source";
      namedWindow( source_window, CV_WINDOW_AUTOSIZE );
      imshow( source_window, src );
    
      createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
      thresh_callback( 0, 0 );
    
      waitKey(0);
      return(0);
    }
    
    /** @function thresh_callback */
    void thresh_callback(int, void* )
    {
      Mat threshold_output;
      vector<vector<Point> > contours;
      vector<Vec4i> hierarchy;
    
      /// 阈值化检测边界
      threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
      /// 寻找轮廓
      findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
    
      /// 对每个找到的轮廓创建可倾斜的边界框和椭圆
      vector<RotatedRect> minRect( contours.size() );
      vector<RotatedRect> minEllipse( contours.size() );
    
      for( int i = 0; i < contours.size(); i++ )
         { minRect[i] = minAreaRect( Mat(contours[i]) );
           if( contours[i].size() > 5 )
             { minEllipse[i] = fitEllipse( Mat(contours[i]) ); }
         }
    
      /// 绘出轮廓及其可倾斜的边界框和边界椭圆
      Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
      for( int i = 0; i< contours.size(); i++ )
         {
           Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
           // contour
           drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
           // ellipse
           ellipse( drawing, minEllipse[i], color, 2, 8 );
           // rotated rectangle
           Point2f rect_points[4]; minRect[i].points( rect_points );
           for( int j = 0; j < 4; j++ )
              line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
         }
    
      /// 结果在窗体中显示
      namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
      imshow( "Contours", drawing );
    }
    

    说明

    结果

    1. 结果如下图:

      BRE_0

      BRE_1






    轮廓矩

    目标

    在这节教程中您将学到:

    • 使用OpenCV函数 moments 计算图像所有的矩(最高到3阶)
    • 使用OpenCV函数 contourArea 来计算轮廓面积
    • 使用OpenCV函数 arcLength 来计算轮廓或曲线长度

    原理

    代码

    下面是本节教程源码. 你也可以从 这里 下载.

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    using namespace std;
    
    Mat src; Mat src_gray;
    int thresh = 100;
    int max_thresh = 255;
    RNG rng(12345);
    
    /// 函数声明
    void thresh_callback(int, void* );
    
    /** @主函数 */
    int main( int argc, char** argv )
    {
      /// 读入原图像, 返回3通道图像数据
      src = imread( argv[1], 1 );
    
      /// 把原图像转化成灰度图像并进行平滑
      cvtColor( src, src_gray, CV_BGR2GRAY );
      blur( src_gray, src_gray, Size(3,3) );
    
      /// 创建新窗口
      char* source_window = "Source";
      namedWindow( source_window, CV_WINDOW_AUTOSIZE );
      imshow( source_window, src );
    
      createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
      thresh_callback( 0, 0 );
    
      waitKey(0);
      return(0);
    }
    
    /** @thresh_callback 函数 */
    void thresh_callback(int, void* )
    {
      Mat canny_output;
      vector<vector<Point> > contours;
      vector<Vec4i> hierarchy;
    
      /// 使用Canndy检测边缘
      Canny( src_gray, canny_output, thresh, thresh*2, 3 );
      /// 找到轮廓
      findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_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 ); }
    
      ///  计算中心矩:
      vector<Point2f> mc( contours.size() );
      for( int i = 0; i < contours.size(); i++ )
         { mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }
    
      /// 绘制轮廓
      Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
      for( int i = 0; i< contours.size(); i++ )
         {
           Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
           drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
           circle( drawing, mc[i], 4, color, -1, 8, 0 );
         }
    
      /// 显示到窗口中
      namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
      imshow( "Contours", drawing );
    
      /// 通过m00计算轮廓面积并且和OpenCV函数比较
      printf("\t Info: Area and Contour Length \n");
      for( int i = 0; i< contours.size(); i++ )
         {
           printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength( contours[i], true ) );
           Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
           drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
           circle( drawing, mc[i], 4, color, -1, 8, 0 );
         }
    }
    

    代码说明

    结果

    1. 如下所示:

      MU_0

      MU_1

      MU_2





    多边形测试

    目的

    本教程指导用户:

    理论

    代码

    本教程代码如下所示. 用户也可以点击 这里下载

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    using namespace std;
    
    /** @function main */
    int main( int argc, char** argv )
    {
      /// 创建一个图形     const int r = 100;
      Mat src = Mat::zeros( Size( 4*r, 4*r ), CV_8UC1 );
    
      /// 绘制一系列点创建一个轮廓:
      vector<Point2f> vert(6);
    
      vert[0] = Point( 1.5*r, 1.34*r );
      vert[1] = Point( 1*r, 2*r );
      vert[2] = Point( 1.5*r, 2.866*r );
      vert[3] = Point( 2.5*r, 2.866*r );
      vert[4] = Point( 3*r, 2*r );
      vert[5] = Point( 2.5*r, 1.34*r );
    
      /// 在src内部绘制
      for( int j = 0; j < 6; j++ )
         { line( src, vert[j],  vert[(j+1)%6], Scalar( 255 ), 3, 8 ); }
    
      /// 得到轮廓
      vector<vector<Point> > contours; vector<Vec4i> hierarchy;
      Mat src_copy = src.clone();
    
      findContours( src_copy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
    
      /// 计算到轮廓的距离
      Mat raw_dist( src.size(), CV_32FC1 );
    
      for( int j = 0; j < src.rows; j++ )
         { for( int i = 0; i < src.cols; i++ )
              { raw_dist.at<float>(j,i) = pointPolygonTest( contours[0], Point2f(i,j), true ); }
         }
    
      double minVal; double maxVal;
      minMaxLoc( raw_dist, &minVal, &maxVal, 0, 0, Mat() );
      minVal = abs(minVal); maxVal = abs(maxVal);
    
      /// 图形化的显示距离
      Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
    
      for( int j = 0; j < src.rows; j++ )
         { for( int i = 0; i < src.cols; i++ )
              {
                if( raw_dist.at<float>(j,i) < 0 )
                  { drawing.at<Vec3b>(j,i)[0] = 255 - (int) abs(raw_dist.at<float>(j,i))*255/minVal; }
                else if( raw_dist.at<float>(j,i) > 0 )
                  { drawing.at<Vec3b>(j,i)[2] = 255 - (int) raw_dist.at<float>(j,i)*255/maxVal; }
                else
                  { drawing.at<Vec3b>(j,i)[0] = 255; drawing.at<Vec3b>(j,i)[1] = 255; drawing.at<Vec3b>(j,i)[2] = 255; }
              }
         }
    
      /// 创建窗口显示结果
      char* source_window = "Source";
      namedWindow( source_window, CV_WINDOW_AUTOSIZE );
      imshow( source_window, src );
      namedWindow( "Distance", CV_WINDOW_AUTOSIZE );
      imshow( "Distance", drawing );
    
      waitKey(0);
      return(0);
    }
    

    解释

    结果

    1. 输出如下:

      PPT_0

      PPT_1




    from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc
    展开全文
  • 图像处理-椭圆检测

    千次阅读 2019-05-16 10:11:18
    边界聚类椭圆检测算法经典椭圆检测方法投票(聚类)方法随机hough变换椭圆检测算法最优化方法基于弧段的方法边界聚类算法流程预处理边界像素连接线段列提取线段列旋转方向统一凹点和角点检测圆弧聚类再配对直接最小...

    经典椭圆检测方法

    椭圆检测算法经过多年的研究发展,已经基本形成一个较完整的体系。它们大致可以分为三类即投票(聚类)、最优化、基于弧段的方法。

    投票(聚类)方法

    椭圆因为有中心位置坐标、长短轴长度、倾斜角五个参数,标准霍夫变换有较强的鲁棒性,但对内存要求高,运算效率低,不太现实。霍夫变换类算法以霍夫变换为算法基础,经过不同国家研究人员多年的不懈努力研究,如今已衍生出很多改进算法,它们各有优劣。随机霍夫变换算法相对标准霍夫变换计算速度有较大提升,但检测相互遮挡的椭圆时准确度低。

    随机hough变换椭圆检测算法

    随机椭圆检测结合使用了了最小二乘法和Hough变换算法。第一步预处理,获得较理想的边缘图。第二步随机选取三个点,取这三点为中心相同大小的邻域中所有点,用最小二乘法把它们拟合成一个椭圆。如图2-3所示。第三步从边缘点中再随机选取第四个点,判断此点是否在拟合出的圆上。若是,则认为该椭圆是真实椭圆的可能性较大,接着收集证据,验证该椭圆的真实性。

    图2-3 随机选点示意图
    算法具体过程如下(从第二步开始):
    1.把边缘检测得到的点收进集合V中,失败计数器f初始值设为0。设定5个阈值,分别是能容忍的失败次数最大值Tf,检测进行时对V中边缘点数量的要求阈值Tem,随机选取的三点之间两两距离最小值Ta,随机选取的第四点到可能椭圆边界距离的最大值Td,以及椭圆残缺比率阈值Tr。
    2.np表示集合V中剩余的点的数量,当np小于Tem时或当失败次数f大于Tf时停止检测,算法终止;否则从V中随机取四点,并从V中删除这四点。
    3.若用来求解椭圆参数的三个点两两之间距离都大于Ta,拟合出椭圆,计算第四个点到该椭圆边界的距离,若距离小于Td,执行第4步;若不满足两者之一,将这四个点返回到V中,失败次数加一,回到第2步执行。
    4.设E为第3步拟合出来的椭圆,初始化满足阈值Td的点的个数num。遍历V中点,计算并判断它们到椭圆E的边界的距离是否小于Td,若是则num=num+1,并将该点从V中除去,直到遍历完成。
    5.若num>=Tr*K,其中K为椭圆E的周长,那么跳转到第6步;否则认为椭圆E不是真实的椭圆,将第4步和第2步中删除的num+4个点返回V中,并跳转到第2步。
    6.认为椭圆E是一个真实存在的椭圆,f置0,并跳转到第2步。
    随机hough变换的优缺点如下:
    第一,由于该算法是基于最小二乘法,所以一方面检测结果往往比真实椭圆小而且对噪声敏感,但是另一方面当预处理效果较好时检测精度很高。
    第二,由于该算法基于随机采样,所以一方面可能会有所选点距离较近的情况造成拟合出的椭圆偏差较大,但是另一方面因为随机采样的灵活性检测速度提升了。
    第三,一方面当参数选取的较好时检测又快有准确;另一方面,由于该算法严格由参数Ta,Td,Tr控制而且这些参数不易取到合适值,所以会出现不合适的参数不仅增加计算量,而且增加误检机会的情况。

    最优化方法

    最优化类方法优点在其精度上,缺点是其一次只能处理一个图形,即此前要对图像信息进行分类分离。AndrewFitzgibb等人提出了直接最小二乘法椭圆拟合算法。该方法能保证拟合出来的一定是椭圆。但该方法受到孤立点和噪声点的影响。目前最优化算法多与其他算法一起结合使用。

    基于弧段的方法

    基于边界聚类的椭圆检测方法结合使用了基于弧段的方法和最小二乘法。从边界图提取圆弧,再经过过滤、聚类,最终用最小二乘法拟合出椭圆。该方法能有效应对多个椭圆、椭圆相互遮挡和椭圆部分缺损等复杂情况,因而引起了广泛的注意。

    边界聚类算法流程

    边界聚类算法属于从下往上结构的算法。算法步骤主要分为三步,分别是预处理,边界聚类和直接最小二乘法拟合椭圆三个过程。流程图如下所示:
    在这里插入图片描述

    预处理

    预处理第一步是进行灰度变换。灰度化常用的方法也就是依据亮度方程来实现的,即依据人眼对不同颜色的敏感度不同,对RGB分量以不同系数的加权平均。
    在这里插入图片描述
    第二是降噪。去噪手段对应于噪声的两种分类主要有两种。噪声功率谱符合高斯函数时用可以用高斯平滑模板平滑。由于脉冲干扰等产生的噪声(即椒盐噪声)可以采用中值滤波去除。
    第三步是边界检测。通常图像中边界点都是图像中亮度梯度比较大的点,这些点包含了我们检测要用到的图像特征信息。边界检测最常用的方法是Canny算法。该算法主要分四个部分。一、降噪。方法是让原始图像和所用的高斯模板作卷积,模板在使用前指定了标准差。 二、寻找亮度梯度。Canny算法使用4个模板检测边缘的方向,,它们分别是水平、垂直、主对角线和副对角线方向;遍历圆图上的每个像素点,让原始图像中以该点为中心以该模板为窗口内的所有点与该模板作卷积,我们就从原图获得了各个点亮度梯度图和亮度梯度的方向。三、非极大值抑制。该过程目的是获得单像素的候选边缘,主要操作是将非零像素点所在的区域进行细化。具体过程如下:对于图3-2中一点P(x,y),计算P点梯度方向与其8-连通邻域点所组成的正方形的交点 (x1,y1)和(x2,y2)。如图2-5,交点坐标通过插值法得到。如果中间点的值大于这两个交点值,那么P点值不变,如若不然置零。四、滞后阈值操作。它需要设置两个阈值t1与t2。t1等于边界像素数除以总像素数,这些点称之为强边缘像素。t2等于t1除以2, t2和 t1之间的点称之为弱边缘像素。最后通过将8-连通的弱像素集成到强像素,再把它们连接起来,得到边界图。
    在这里插入图片描述
    第四步是二值化。二值化的效果几乎完全取决于分割阈值的选取。所以自动寻找最佳分割阈值的方法就显得十分关键。找到图片二值化的一个合适的分割阈值的一种方法是按图像的灰度特性,将图像分成背景和目标两部分,背景和目标之间的类间方差最大的分割意味着错分概率最小。MATLAB 的Graythresh函数就是使用该方法来获得一个自适应阈值作为二值化的分割依据。
    第五步,二值化后,因为椭圆弧附近的非相关像素会严重影响检测结果,因此为了大幅度减少非相关像素,本文用形态学的腐蚀操作来得到细化的边界。

    边界像素连接

    采用Kovesi的边界连接算法,以8-邻域连通准则从上至下,从左至右扫描二值图像,将边界像素连接为有向边界列。然后采用边界列中像素数阈值条件去除像素数较小的集合。因为若边界列像素数少于阈值数,则很有可能是噪声或背景,应当删除。具体步骤如下:
    1.以8-邻域连通准则从上至下,从左至右扫描二值图像,按连通区域对图像中的像素点聚类。
    2.寻找每一个连通域中边界像素中所有的结束点和分叉点(分叉点是三条以上曲线的交点)并存储。
    3.以这些结束点和分叉点为结束标志,让每一个连通域中的像素点集合分割为遇到结束点和连接点就断开的小集合。
    4.删除这些集合中像素数小于某一阈值的部分。

    线段列提取

    因为图像光栅化难以获得准确的切线,而后续过程需要用到圆弧的切线,所以要进行线段拟合,即用多段折线代替原来的圆弧。具体步骤如下:
    1.取边界像素连接成的第i条有向边界列,判断是否超过边界列总数目total,若不是进行步骤2,若是终止算法。
    2.判断该边界列是否已经完成处理,若为否则进行第3步;若是则i=i+1,重新进行步骤2.
    3.从其中第三个点开始,计算第一个点到这个点(记为点j)的连线方程,并依次判断第一个点和该点之间的所有点到该连线的距离,若所有距离均小于某一阈值,则j=j+1,重新进行步骤3,否则该有向边界列从该处断开,前面部分只保留第一个点和第j个点,前面j个点构成的连线用第一点和第j点之间的直线连线代替;后面部分仍然记为边界列i,
    4.判断步骤3中断开的有向边界列后面部分像素数是否小于某阈值,若是则删除掉,否则不处理。最后跳转到步骤1。
    完成这一个过程后一个连通域的的像素点构成的曲线就变成了其中部分像素点构成的一条折线。经过这个过程虽然像素信息损失了一部分,但是求取圆弧切线的精度从某种意义上说提高了,因为没有了光栅化效应。而且数据少了处理变得简单。再采用线段数阈值条件去除较短的线段列。若线段数数少于阈值数,则很有可能是噪声或背景或者进行拟合时误差过大,因此须删除。

    线段列旋转方向统一

    本文将所有线段列旋转方向统一为逆时针方向。
    假设图3-4中的黑点为线段列中的点,箭头代表线段列的方向,P1(x1,y1),P2(x2,y2),P3(x3,y3)为线段列中连续的三个像素,像素都引入z坐标,且令其为0,则P1(x1,y1,0),P2(x2,y2,0),P3(x3,y3,0),空间向量
    P1P2=(x2-x1,y2-y1,0)         (3-2)P2P3=(x3-x2,y3-y2,0)         (3-3)
    向量积
    P1P2×P2P3=|■(i ⃗&j ⃗&k ⃗@x2-x1&y2-y1&0@x3-x2&y3-y2 &0)|=(0,0,(x2-x1)(y3-y2)-(x3-x2)(y2-y1))         (3-4)
    对一个线段列中除去首尾两个点的所有点像P2点一样计算并判别和存储,若小于0的次数最多,则认为线段列的旋转方向是顺时针,将线段列中的点逆序处理;若大于0的次数最多,则认为线段列的旋转方向是逆时针。
    在这里插入图片描述

    凹点和角点检测

    在确定线段列的旋转方向为逆时针方向后,检测凹点和角点方法同前面统一线段列旋转方向类似,对一个线段列中除去首尾两个点的所有点计算P1P2×P2P3并判断向量积第三个分量的大小,若小于0,则P2为凹点。因为边界波动可能引入冗余凹点也即因边界检测误差可能错判一些正常点为凹点而进修分割会导致检测率下降,所以增加一个角度判断过程,即前面向量积为0并且P1P2和P2P3的夹角大于某阈值,才为凹点,这样选择合适的阈值可保证凹点检测的准确性。若向量积大于0且P1P2和P2P3的夹角大于另一阈值,则认为该点角度变化过大,是角点,线段列有很大可能性不是椭圆弧,而有可能是三角形、矩形等图形的边角,因此从该点分割该线段列。分割完成后,过滤掉含点数较少的线段列,即可除掉部分非椭圆弧。留下的线段列认为是椭圆弧,参加后续的聚类。如图3-5所示,左上角的P2很可能是凹点,右下角的P2很可能是角点。
    在这里插入图片描述

    圆弧聚类

    圆弧聚类是将属于同一椭圆但是分开的两条或多条椭圆弧进行聚类。在进行聚类前,首先要判断弧段的完整度。一般用弧段的首尾端点P1,P3与中点P2构成的两向量P2P1,P2P3的夹角的大小来进行判断。夹角越小,一般该椭圆弧越完整,夹角越大,一般认为椭圆弧缺损越严重。设定一个阈值,当夹角小于该阈值时认为该弧段已经足够完整,仅仅靠该弧段上的点就可以较准确地拟合出真实存在的椭圆,因此该弧段不需要参与后面的聚类过程。如果希望该阈值自适应,在划分待聚类椭圆弧和不须聚类的弧(较完整弧)之前,先要确定该阈值。用直接最小二乘法拟合该弧所在的椭圆,若较圆,为了减小的误差,应使阈值夹角稍微大一些,如90度;若该弧所在的椭圆较扁,应使阈值夹角稍微小一些,如60度。接下来才根据该自适应阈值进行对圆弧判断。当大于阈值时认为该弧段上的点过少,不足以拟合出准确的椭圆,需要找到和该弧段属于同一椭圆的弧段然后用它们所有的点一起拟合出一个椭圆。经过此判断过程,椭圆弧就被分成两组。把需要参加聚类的椭圆弧按照含点数的数目由多到少进行排列,下面的过程都按照数目多的弧段优先的顺序进行。
    对于待聚类的椭圆弧,先要定义其搜索区域,由于椭圆是封闭图形,所以整个椭圆可以确定是在其任何一部分弧和弧两端点的切线所在的射线包围起来的区域里面,属于该椭圆的其他弧以确定是在该弧对应的弦和弧两端点的切线所在的射线包围起来的区域里面。这就是我们搜索的区域。在图中a1的搜索区域也就是射线l1,l2,弦l3和图像边缘范围内的区域,在这个区域里面找弧,可以缩小搜寻的范围,提高效率。判断一条弧是否在待聚类椭圆弧的搜索区域里面我们只需取这条弧的首末端点j3,j4是否在搜索区域。方法是分别求过这两点同时平行于待聚类椭圆弧对应弦l3的直线和切线的交点,若交点分别有两个,交点都在射线上且这两个端点在对应两个交点之间则该弧段在搜索区域内。图中明显a2,a3,a4在a1的搜索区域内而a5不在。
    在这里插入图片描述
    待聚类椭圆弧找到待配对的圆弧后用两种约束条件判断它们到底是否属于同一椭圆。约束一是利用一个椭圆任意两段弧弧中点之间的距离大于一个弧中点到另一个弧首末端点连线的中点之间的距离,在图中即为
    在这里插入图片描述
    用来去除图中a2类型的椭圆弧。右图(b)不满足,左图(a)同时满足这两个关系,进入下一步,再用约束二进行判断。
    在这里插入图片描述
    约束二是点到拟合椭圆边界距离条件。我们只需要让两条线段列中的点一起参与椭圆拟合,按照下面公式计算所有这些点到拟合出椭圆边界的距离。设置一距离阈值,当d_i小于该阈值认为该点落在该椭圆上,否则该点不在这个椭圆上。统计d_i中小于某一阈值的点的数量,若大于某一比例(比例阈值),则认为这两条弧属于同一椭圆,否则不属于同一椭圆。判定后将属于同一椭圆的弧段聚类到一起。
    在这里插入图片描述
    其中
    其中:x^'=(x_i-x_0)cos⁡〖θ+(y_i-y_0)sin⁡θ 〗,y^'=-(x_i-x_0)sin⁡〖θ+(y_i-y_0)cos⁡θ 〗。
    在这里插入图片描述
    如上图(a)中的参与拟合的点较多都落在拟合的椭圆上,所以有较大可能满足约束二条件;(b)中大多数拟合点点离拟合出的椭圆边界有一定距离,有较大可能不满足约束条件二。
    如果希望这两个阈值改为自适应的,方法是先拟合出椭圆,判断椭圆大小。若椭圆较小,应适当降低限制,即增大距离阈值,减小比例阈值;若椭圆较大,应适当提高限制,即减小距离阈值,增大比例阈值。本算法中选取的是,若椭圆的短轴小于50则距离阈值为0.05,比例阈值设为0.7;否则前者取0.03,后者取0.8。

    再配对

    聚类后的弧和较完整弧或者两个较完整弧可能属于同一椭圆但是在前面的步骤它们只是被分开了并没有配对,所以有必要增加再匹配过程,增加检测准确度。匹配方法还是约束条件二的方法。因为该方法和原算法的去伪过程相似,所以经过该方法后无需再去伪。

    直接最小二乘法椭圆拟合

    前面的很多步骤都删除了像素点较少的集合,或者用少数点代替了边界列中的很多点,或者是分割后再删除点数较少的集合的,这些操作到椭圆拟合这一步实际上基本上去除了所有的背景和噪声,甚至包括一部分有用信息。所以即使对噪声和孤立点敏感的直接最小二乘法也可以用来拟合椭圆,而且因为该方法对椭圆缺损不敏感,所以非常适合。

    实验效果

    边界聚类算法检测结果

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    边界聚类算法和随机霍夫变换算法比较

    用时比较在这里插入图片描述
    检测结果比较
    左图是选点情况;右图是拟合的椭圆和真实椭圆的差异
    在这里插入图片描述
    随机霍夫变换算法检测结果
    在这里插入图片描述
    边界聚类算法检测结果

    展开全文
  • 图像处理中的椭圆拟合(一)

    万次阅读 2014-03-30 21:18:18
    图像处理中的椭圆检测用处还是挺多的,找到这里来的同学大多是想用椭圆检测来解决某些实际问题吧,所以我就不做介绍,直奔主题。我研究这块也有一段时间了,也查找了挺多资料,貌似通用的椭圆算法还没有,不排除我...

            图像处理中的椭圆检测用处还是挺多的,找到这里来的同学大多是想用椭圆检测来解决某些实际问题吧,所以我就不做介绍,直奔主题。我研究这块也有一段时间了,也查找了挺多资料,貌似通用的椭圆算法还没有,不排除我孤陋寡闻了。前辈提出的算法适用范围比较有限,这个比较有限是相对直线检测来说的。但直接用Hough变换来找椭圆几乎是不可能的事,在5维空间里做投票,想想都觉得可怕。于是有人想到用随机Hough变换。这是一种很合理的方法,我就是这么做的,不过这种方法有个不足之处,后面会讲到。这里先介绍这方法的流程。

            二次曲线的一般方程为:,其中(x,y)为图像的坐标空间,BCDEF是二次曲线的参数。当满足时二次曲线为椭圆。方程中需要求解的参数有5个,在随机Hough变换过程中要至少采集5个点,得到5个方程以求得这5个参数。若方程有解且满足约束条件,则将解加入参数空间进行累积。思路是比较简单的,下面边贴代码边解释(P.S. 代码仅供参考)。

    void FindEllipse(TImage* OrgGrayImg)
    {
    int ImgHeight = OrgGrayImg->nHeight;
    	int ImgWidth = OrgGrayImg->nWidth;
    	unsigned char * Img = OrgGrayImg->pImage; // 输入图像确保是二值图像
    
    srand((unsigned)time(NULL));
    
    	int totalPt = 0;// 用于统计样本点的个数
    
    	for (i = 0; i < ImgHeight - 0; i++)
    	{
    		unsigned char *imgdata = Img + i * ImgWidth;
    		for (j = 0; j < ImgWidth - 0; j++)
    		{
    			if (!imgdata[j])
    				totalPt ++;
    		}
    	}
    
    	if (totalPt < 5)
    		return;
    
    	POINT * seq;
    	seq = new POINT [totalPt];
    
    	int count = 0;
    	for (i = 0; i < ImgHeight; i++)
    	{
    		unsigned char *data = Img + i * ImgWidth;
    		for (j = 0; j < ImgWidth; j++)
    		{
    			if (!data[j])
    			{
    				seq[count].x = j;
    				seq[count].y = i;
    				count ++;
    			}
    		}
    	}
    
    	double Para[5];	// 存放结果(5个参数A,B,C,D,E)的数组
    	int Angle_V[360]={0};// 椭圆倾斜角参数空间
    	int *Center_XV = new int[ImgWidth];// 椭圆中心点x坐标参数空间
    	int *Center_YV = new int[ImgHeight];// 椭圆中心点y坐标参数空间
    	int *A_axis_V = new int[max(ImgWidth,ImgHeight)/2];// 椭圆长轴参数空间
    	int *B_axis_V = new int[max(ImgWidth,ImgHeight)/2];// 椭圆短轴参数空间
    	
    	memset(Center_XV,0,sizeof(int)*ImgWidth);
    	memset(Center_YV,0,sizeof(int)*ImgHeight);
    	memset(A_axis_V,0,sizeof(int)*max(ImgWidth,ImgHeight)/2);
    	memset(B_axis_V,0,sizeof(int)*max(ImgWidth,ImgHeight)/2);
    
    	double Theta,X_c,Y_c,A_axis,B_axis;
    
    int loop = 1;// 成功求出参数的迭代次数
    	int looptop = loop * 1;// 总的迭代次数(也就是控制计算时间的上限,以免陷入无限循环)
    while(loop > 0 && looptop > 0)
    {
    looptop --;
    	int idx;
    	for (count = totalPt; count > 0; count--)// 打乱样本点排列的顺序
    	{
    		POINT ptrtmp;
    		idx = rand() % count;
    		
    		ptrtmp = seq[idx];
    		seq[idx] = seq[count-1];
    		seq[count-1] = ptrtmp;		
    	}
    
    	double PioMatrix[5*5];
    	for (i = 0; i < 5; i++)
    	{
    		PioMatrix[i*5] = seq[i].x * seq[i].x;
    		PioMatrix[i*5 + 1] = 2 * seq[i].x * seq[i].y;
    		PioMatrix[i*5 + 2] = seq[i].y * seq[i].y;
    		PioMatrix[i*5 + 3] = 2 * seq[i].x;
    		PioMatrix[i*5 + 4] = 2 * seq[i].y;
    	}
    
    	if (GaussJordanInv(PioMatrix,5) == false)// Gauss-Jordan求逆
    		continue;
    	double sum;
    	for (i = 0; i < 5; i++)
    	{
    		sum = 0;
    		for (j = 0; j < 5; j++)
    		{
    			sum +=  -(PioMatrix[i*5 + j]);
    		}
    		Para[i] = sum;
    	}
    
    	if (pow(Para[1],2) - Para[0] * Para[2] > 0)
    			continue;
    
    		if (fabs(Para[0] - Para[2]) < 1e-20)
    			Theta = 1.570796326;
    		else if (Para[0] > Para[2])
    			Theta = 0.5 * (atan(2.0 * Para[1] / (Para[0] - Para[2])) + PI);
    		else
    			Theta = 0.5 * (atan(2.0 * Para[1] / (Para[0] - Para[2])));
    
    		X_c = (4.0 * Para[1] * Para[4] - 4.0 * Para[2] * Para[3]) / (4.0 * Para[0] * Para[2] - 4.0 * Para[1] * Para[1]);
    		Y_c = (4.0 * Para[1] * Para[3] - 4.0 * Para[0] * Para[4]) / (4.0 * Para[0] * Para[2] - 4.0 * Para[1] * Para[1]);
    		A_axis = 2 * (Para[0] * pow(X_c,2) + Para[2] * pow(Y_c,2) + 2 * Para[1] * X_c * Y_c - 1) 
    						/ (Para[0] + Para[2] - sqrt(pow(Para[0] - Para[2],2) + pow(2.0 * Para[1],2)));
    		B_axis = 2 * (Para[0] * pow(X_c,2) + Para[2] * pow(Y_c,2) + 2 * Para[1] * X_c * Y_c - 1) 
    						/ (Para[0] + Para[2] + sqrt(pow(Para[0] - Para[2],2) + pow(2.0 * Para[1],2)));
    		
    		A_axis = sqrt(A_axis);	//长轴
    		B_axis = sqrt(B_axis);	//短轴
    
    		int AngleTmp = (int)(Theta * 180 / PI + 360 + 0.5) % 360;
    		Angle_V[AngleTmp]++;
    		
    		if (X_c < 0 || Y_c < 0 || A_axis < 0 || B_axis < 0)
    			continue;
    		if (X_c >= ImgWidth || Y_c >= ImgHeight || A_axis > max(ImgWidth,ImgHeight)/2 || B_axis > max(ImgWidth,ImgHeight)/2)
    			continue;
    
    		if (X_c >= 0 && X_c < ImgWidth)
    			Center_XV[(int)X_c]++;
    		if (Y_c >= 0 && Y_c < ImgHeight)
    			Center_YV[(int)Y_c]++;
    		if (A_axis >= 0 && A_axis < max(ImgWidth,ImgHeight)/2)
    			A_axis_V[(int)A_axis]++;
    		if (B_axis >= 0 && B_axis < max(ImgWidth,ImgHeight)/2)
    			B_axis_V[(int)B_axis]++;		
    		loop--;
    }
    	
    	int Angle,Ai,Bi,Cx,Cy;
    	//	Angle
    	int MaxPara = 0;
    	for (i = 0; i < 360; i++)
    	{
    		if (MaxPara < Angle_V[i])
    		{
    			MaxPara = Angle_V[i];
    			Angle = i;
    		}
    	}
    	//	Cy
    	MaxPara = 0;
    	for (i = 0; i < ImgHeight; i++)
    	{
    		if (MaxPara < Center_YV[i])
    		{
    			MaxPara = Center_YV[i];
    			Cy = i;
    		}
    	}
    	//	Cx
    	MaxPara = 0;
    	for (i = 0; i < ImgWidth; i++)
    	{
    		if (MaxPara < Center_XV[i])
    		{
    			MaxPara = Center_XV[i];
    			Cx = i;
    		}
    	}
    	//	Ai
    	MaxPara = 0;
    	for (i = 0; i < max(ImgWidth,ImgHeight)/2; i++)
    	{
    		if (MaxPara < A_axis_V[i])
    		{
    			MaxPara = A_axis_V[i];
    			Ai = i;
    		}
    	}
    	//	Bi
    	MaxPara = 0;
    	for (i = 0; i < max(ImgWidth,ImgHeight)/2; i++)
    	{
    		if (MaxPara < B_axis_V[i])
    		{
    			MaxPara = B_axis_V[i];
    			Bi = i;
    		}
    	}
    
    	delete[] Center_XV;
    	delete[] Center_YV;
    	delete[] A_axis_V;
    	delete[] B_axis_V;
    
    
    	double sma = SinMem[Angle];
    	double cma = CosMem[Angle];
    	for (int n = 0; n < 360; n++)
    	{
    		i = (Bi) * CosMem[360 - n];
    		j = (Ai) * SinMem[360 - n];
    		
    		int x,y;
    		x = (j * cma - i * sma) + Cx;
    		y = (i * cma + j * sma) + Cy;
    		
    		Mask[y * ImgWidth + x] = 0;
    	}
    	delete[] Mask;
    	delete[] seq;
    }
    

    测试结果:

    原图:

    拟合结果(虚线为拟合的椭圆):

    前面说到这种方法有缺陷,请看下面的情形:

    原图:

    拟合结果:

           当样本点只集中在椭圆的一边时,随机5点的hough变换总会拟合错误,实际应用中往往会发生这样的情况。这是因为公式错了吗?于是我单独提取出五点做测试,即只做一次迭代。测试结果如下图所示。图中实线为实际椭圆,我是用画图工具拖出来的“完美椭圆”,用橡皮擦擦掉一大半部分,最后再做一点旋转。打交叉的是取样的5点,虚线是用这5点代入公式求得的拟合椭圆。可见求得的椭圆穿过了5个点,表明不是求解错了,可就是跟实际的有很大差别。唯一的解释是取样点不在我们想要的椭圆上,也就是说即使是用画图工具拖出来看似完美的椭圆并不完美,这是因为样本点的坐标是整型,精度很低。所以随机5点的hough变换存在很严重的系统误差,当取样点分散在椭圆上下左右时,这种误差会比较小,当集中在某个区域时,误差就会非常大。

       

            解决办法就是多采几个点,然后用最小二乘法求解。

    下图是10点随机采样的结果:

    下图是将所有点一起计算的结果:

    可见,采样点越多,拟合度越好。但是一次取样的点越多,这些点落入相同椭圆的概率就越小。这就需要一些手段把椭圆的边缘从噪声中提取出来。至于最小二乘法的椭圆检测算法我将另开一贴来讨论。

     

     

     


     

    展开全文
  • 检测图像中的椭圆 并求其长短轴...

    万次阅读 2009-07-05 20:29:00
    运行环境 BCB 6.0~ 需要OPENCV 库支持 CvMemStorage* storage = cvCreateMemStorage(0);CvSeq* contours;CvBox2D s;char string1[22];char string2[22];IplImage* img = cvLoadImage( "Elli
  • 图像处理——椭圆肤色模型

    千次阅读 2017-09-27 11:02:51
    图像处理——椭圆肤色模型 这里参数的说明参考《基于分裂式K均值聚类的肤色检测方法 》 http://download.csdn.net/detail/wobuaishangdiao/4378026 《皮肤检测技术的研究及改进》皮肤检测—-肤色椭圆模型 肤色...
  • 利用opencv进行图像处理,提取椭圆圆心处理 写这个是因为项目正好在做这个,所以简单写写提取椭圆圆心坐标的代码,用的软件是VS。 首先介绍一下步骤,直接从图像处理开始 1,二值化处理(threhold()) 2,高斯滤波...
  • 椭圆拟合matlab代码

    2020-07-28 23:31:44
    椭圆拟合程序的源码,用于图像处理。 Description There are two main methods for least squares ellipse fitting: 1) Minimise algebraic distance, i.e. minimise sum(F(x)^2) subject to some constraint, ...
  • 图像处理之霍夫变换(直线检测算法)

    万次阅读 多人点赞 2016-11-01 20:35:59
    图像处理之霍夫变换(直线检测算法) 霍夫变换是图像变换中的经典手段之一,主要用来从图像中分离出具有某种相同特征的几何 形状(如,直线,圆等)。霍夫变换寻找直线与圆的方法相比与其它方法可以更好的减少噪 声...
  • OpenCV3基础——几种基本的图像处理

    万次阅读 多人点赞 2019-03-12 18:56:36
    虽然单单要做车牌号识别的话不需要特别多种类的图像处理,但是我们不能只是为了这么一个目标去学习,所以这次就讲一些OpenCV里基本的图像处理,大家以后可以根据需求使用不同的图像处理。 一、图像显示 这一步在...
  • 看看图片处理效果: 代码: PImage origin, pic; int diamt; int spacing; void setup() { size(960, 540, P2D); origin=loadImage("C:\\helecopter.jpg"); pic= loadImage("C:\\helecopter.jpg"); pic.filter...
  • 而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。 数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本...
  • 椭圆拟合之最小二乘法 原理: 代码: QImage inputImage : 输入图像; float ellipse : 输出椭圆的各个参数 bool Vertical : 是否垂直 bool total : 椭圆是否有上下部分,还是只是椭圆的一段曲线 void Ellipse...
  • 自《图像处理中的数学修炼》原书第一版于2017年2月上市以来,加印重印多次,在京东和当当等主流在线购书网站上已经累计有超过3200个有效购买评论,并且在这两个网站上的好评度都超过99%。结合第一版书籍读者给出的...
  • 椭圆形显示图像
  • matlab 椭圆拟合程序

    2020-07-29 14:21:25
    ellipsefit为椭圆拟合程序。ellipse1为示例。只要输入点的坐标(无论多少点),此程序就可算出拟合的椭圆方程
  • 图像处理中,经常要用到形态学操作,形态学操作中的结构元素有很多,如点结构 十字架结构 圆结构 矩形结构 椭圆形结构等等 本文将介绍椭圆形结构的实现。(主要结合OpenCV实现) 具体如下: #include #include ...
  • 基于python图像处理API

    万次阅读 2020-04-02 11:19:20
    1.图像处理库 import cv2 as cv from PIL import * 常用的图像处理技术有图像读取,写入,绘图,图像色彩空间转换,图像几何变换,图像形态学,图像梯度,图像边缘检测,图像轮廓,图像分割,图像去噪,图像加...
  • 【前言】图像预处理对于整个图像处理任务来讲特别重要。如果我们没有进行恰当的预处理,无论我们有多么好的数据也很难得到理想的结果。 本篇是视觉入门系列教程的第二篇。整个视觉入门系列内容如下: 理解颜色...
  • 几何畸变图像恢复 OpenCV3 - 数字图像处理作业3 作业3:相同条件下拍到的棋盘图和日历钟表图,尝试建立几何畸变关系,并对它们进行恢复。注意:不能采用椭圆的变换。 算法步骤: 坐标变换: 在畸变和正常图象之间...
1 2 3 4 5 ... 20
收藏数 15,392
精华内容 6,156
关键字:

图像处理 找椭圆