3.3. python数字图像处理(16):霍夫圆和椭圆变换

在极坐标中,圆的表示方式为:

x=x0+rcosθ

y=y0+rsinθ

圆心为(x0,y0),r为半径,θ为旋转度数,值范围为0-359

如果给定圆心点和半径,则其它点是否在圆上,我们就能检测出来了。在图像中,我们将每个非0像素点作为圆心点,以一定的半径进行检测,如果有一个点在圆上,我们就对这个圆心累加一次。如果检测到一个圆,那么这个圆心点就累加到最大,成为峰值。因此,在检测结果中,一个峰值点,就对应一个圆心点。

霍夫圆检测的函数:

skimage.transform.hough_circle(image, radius)

radius是一个数组,表示半径的集合,如[3,4,5,6]

返回一个3维的数组(radius index, M, N), 第一维表示半径的索引,后面两维表示图像的尺寸。

3.3.1. 例1:绘制两个圆形,用霍夫圆变换将它们检测出来。

>>> %matplotlib inline
>>>
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from skimage import draw,transform,feature
>>>
>>> img = np.zeros((250, 250,3), dtype=np.uint8)
>>> rr, cc = draw.circle_perimeter(60, 60, 50)  #以半径50画一个圆
>>> rr1, cc1 = draw.circle_perimeter(150, 150, 60) #以半径60画一个圆
>>> img[cc, rr,:] =255
>>> img[cc1, rr1,:] =255
>>>
>>> fig, (ax0,ax1) = plt.subplots(1,2, figsize=(8, 5))
>>>
>>> ax0.imshow(img)  #显示原图
>>> ax0.set_title('origin image')
>>>
>>> hough_radii = np.arange(50, 80, 5)  #半径范围
>>> hough_res =transform.hough_circle(img[:,:,0], hough_radii)  #圆变换
>>>
>>> centers = []  #保存所有圆心点坐标
>>> accums = []   #累积值
>>> radii = []    #半径
>>>
>>> for radius, h in zip(hough_radii, hough_res):
>>>     #每一个半径值,取出其中两个圆
>>>     num_peaks = 2
>>>     peaks =feature.peak_local_max(h, num_peaks=num_peaks) #取出峰值
>>>     centers.extend(peaks)
>>>     accums.extend(h[peaks[:, 0], peaks[:, 1]])
>>>     radii.extend([radius] * num_peaks)
>>>
>>> #画出最接近的圆
>>> image =np.copy(img)
>>> for idx in np.argsort(accums)[::-1][:2]:
>>>     center_x, center_y = centers[idx]
>>>     radius = radii[idx]
>>>     cx, cy =draw.circle_perimeter(center_y, center_x, radius)
>>>     image[cy, cx] =(255,0,0)
>>>
>>> ax1.imshow(image)
>>> ax1.set_title('detected image')
Text(0.5, 1.0, 'detected image')
_images/sec16_houghcircle_1_1.png

结果图如上:原图中的圆用白色绘制,检测出的圆用红色绘制。

3.3.2. 例2,检测出下图中存在的硬币。

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from skimage import data, color,draw,transform,feature,util
>>>
>>> image = util.img_as_ubyte(data.coins()[0:95, 70:370]) #裁剪原图片
>>> edges =feature.canny(image, sigma=3, low_threshold=10, high_threshold=50) #检测canny边缘
>>>
>>> fig, (ax0,ax1) = plt.subplots(1,2, figsize=(8, 5))
>>>
>>> ax0.imshow(edges, cmap=plt.cm.gray)  #显示canny边缘
>>> ax0.set_title('original iamge')
>>>
>>> hough_radii = np.arange(15, 30, 2)  #半径范围
>>> hough_res =transform.hough_circle(edges, hough_radii)  #圆变换
>>>
>>> centers = []  #保存中心点坐标
>>> accums = []   #累积值
>>> radii = []    #半径
>>>
>>> for radius, h in zip(hough_radii, hough_res):
>>>     #每一个半径值,取出其中两个圆
>>>     num_peaks = 2
>>>     peaks =feature.peak_local_max(h, num_peaks=num_peaks) #取出峰值
>>>     centers.extend(peaks)
>>>     accums.extend(h[peaks[:, 0], peaks[:, 1]])
>>>     radii.extend([radius] * num_peaks)
>>>
>>> #画出最接近的5个圆
>>> image = color.gray2rgb(image)
>>> for idx in np.argsort(accums)[::-1][:5]:
>>>     center_x, center_y = centers[idx]
>>>     radius = radii[idx]
>>>     cx, cy =draw.circle_perimeter(center_y, center_x, radius)
>>>     image[cy, cx] = (255,0,0)
>>>
>>> ax1.imshow(image)
>>> ax1.set_title('detected image')
Text(0.5, 1.0, 'detected image')
_images/sec16_houghcircle_4_1.png

椭圆变换是类似的,使用函数为:

skimage.transform.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 表示椭圆方向。

3.3.3. 例:检测出咖啡图片中的椭圆杯口

>>> import matplotlib.pyplot as plt
>>> from skimage import data,draw,color,transform,feature
>>>
>>> #加载图片,转换成灰度图并检测边缘
>>> image_rgb = data.coffee()[0:220, 160:420] #裁剪原图像,不然速度非常慢
>>> image_gray = color.rgb2gray(image_rgb)
>>> edges = feature.canny(image_gray, sigma=2.0, low_threshold=0.55, high_threshold=0.8)
>>> #执行椭圆变换
>>> 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) #在原图中用蓝色表示检测出的椭圆
>>> #分别用白色表示canny边缘,用红色表示检测出的椭圆,进行对比
>>> edges = color.gray2rgb(edges)
>>> edges[cy, cx] = (250, 0, 0)
>>> 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                                Traceback (most recent call last)

File /usr/lib/python3/dist-packages/IPython/core/formatters.py:339, in BaseFormatter.__call__(self, obj)
    337     pass
    338 else:
--> 339     return printer(obj)
    340 # Finally look for special method names
    341 method = get_real_method(obj, self.print_method)


File /usr/lib/python3/dist-packages/IPython/core/pylabtools.py:151, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    148     from matplotlib.backend_bases import FigureCanvasBase
    149     FigureCanvasBase(fig)
--> 151 fig.canvas.print_figure(bytes_io, **kw)
    152 data = bytes_io.getvalue()
    153 if fmt == 'svg':


File /usr/lib/python3/dist-packages/matplotlib/backend_bases.py:2314, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2308     renderer = _get_renderer(
   2309         self.figure,
   2310         functools.partial(
   2311             print_method, orientation=orientation)
   2312     )
   2313     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2314         self.figure.draw(renderer)
   2316 if bbox_inches:
   2317     if bbox_inches == "tight":


File /usr/lib/python3/dist-packages/matplotlib/artist.py:74, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     72 @wraps(draw)
     73 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74     result = draw(artist, renderer, *args, **kwargs)
     75     if renderer._rasterizing:
     76         renderer.stop_rasterizing()


File /usr/lib/python3/dist-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:


File /usr/lib/python3/dist-packages/matplotlib/figure.py:3082, in Figure.draw(self, renderer)
   3079         # ValueError can occur when resizing a window.
   3081 self.patch.draw(renderer)
-> 3082 mimage._draw_list_compositing_images(
   3083     renderer, self, artists, self.suppressComposite)
   3085 for sfig in self.subfigs:
   3086     sfig.draw(renderer)


File /usr/lib/python3/dist-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []


File /usr/lib/python3/dist-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:


File /usr/lib/python3/dist-packages/matplotlib/axes/_base.py:3100, in _AxesBase.draw(self, renderer)
   3097         a.draw(renderer)
   3098     renderer.stop_rasterizing()
-> 3100 mimage._draw_list_compositing_images(
   3101     renderer, self, artists, self.figure.suppressComposite)
   3103 renderer.close_group('axes')
   3104 self.stale = False


File /usr/lib/python3/dist-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []


File /usr/lib/python3/dist-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:


File /usr/lib/python3/dist-packages/matplotlib/image.py:641, in _ImageBase.draw(self, renderer, *args, **kwargs)
    639         renderer.draw_image(gc, l, b, im, trans)
    640 else:
--> 641     im, l, b, trans = self.make_image(
    642         renderer, renderer.get_image_magnification())
    643     if im is not None:
    644         renderer.draw_image(gc, l, b, im)


File /usr/lib/python3/dist-packages/matplotlib/image.py:949, in AxesImage.make_image(self, renderer, magnification, unsampled)
    946 transformed_bbox = TransformedBbox(bbox, trans)
    947 clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on()
    948         else self.figure.bbox)
--> 949 return self._make_image(self._A, bbox, transformed_bbox, clip,
    950                         magnification, unsampled=unsampled)


File /usr/lib/python3/dist-packages/matplotlib/image.py:553, in _ImageBase._make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification, unsampled, round_to_pixel_border)
    551     A = _rgb_to_rgba(A)
    552 alpha = self._get_scalar_alpha()
--> 553 output_alpha = _resample(  # resample alpha channel
    554     self, A[..., 3], out_shape, t, alpha=alpha)
    555 output = _resample(  # resample rgb channels
    556     self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
    557 output[..., 3] = output_alpha  # recombine rgb and alpha


File /usr/lib/python3/dist-packages/matplotlib/image.py:207, in _resample(image_obj, data, out_shape, transform, resample, alpha)
    205 if resample is None:
    206     resample = image_obj.get_resample()
--> 207 _image.resample(data, out, transform,
    208                 _interpd_[interpolation],
    209                 resample,
    210                 alpha,
    211                 image_obj.get_filternorm(),
    212                 image_obj.get_filterrad())
    213 return out


ValueError: Unsupported dtype
<Figure size 800x400 with 2 Axes>

霍夫椭圆变换速度非常慢,应避免图像太大。

_images/1.1_x8fa.png