【问题标题】:Circular mask with ratio on boundaries具有边界比率的圆形掩模
【发布时间】:2021-11-21 01:04:00
【问题描述】:

我知道我必须付出一些努力,但我真的不知道如何解决这个问题。

我知道如何创建圆形蒙版:https://stackoverflow.com/a/44874588/2681662

在上面链接中的示例中,掩码是一个布尔数组。意味着它显示像素是否在我想称之为离散的圆圈下方。

但是我想知道每个像素中有多少在圆圈下方。所以基本上我会有一个浮点掩码数组,在圆圈的边界我会有一个值显示圆圈下有多少像素(百分比)。我想称之为连续。

请注意,给出的数字不是计算出来的,是眼球 估计。

如果有人能指出正确的方向,我将不胜感激。

【问题讨论】:

    标签: python image-processing mask


    【解决方案1】:

    我要感谢你们所有人。特别是@Yves Daoust,您为我指明了正确的方向。我想为那些面临类似问题的人分享我的解决方案:

    我的解决方案使用了intersections。如果我能找到一个矩形和一个圆之间的交集区域,我也许可以将一个像素视为一个矩形,而圆显然就是圆。

    两个形状的交集:

    为此,您可以使用匀称:

    使用points and adds some buffer (radius) to it

    from shapely.geometry import Point
    circle = Point(centerx, centery).buffer(r)
    

    创建一个形状匀称的矩形提供box:

    from shapely.geometry import box
    rect = box(minx=min_x, miny=min_y, maxx=max_x, maxy=max_y)
    

    人们可以计算每个形状(从技术上讲是多边形)的许多属性,例如面积和边界点。

    from shapely.geometry import Point
    circle = Point(centerx, centery).buffer(r)
    print(circle.area)
    

    可以计算两个多边形的交集,它会返回一个多边形:

    from shapely.geometry import Point, box
    circle = Point(centerx, centery).buffer(r)
    rect = box(minx=min_x, miny=min_y, maxx=max_x, maxy=max_y)
    intersection = circle.intersection(rect)
    

    像素是边长为 1 个单位(像素)的正方形。所以一个像素和任何其他形状的交集区域会产生一个值[0, 1],这就是我们要寻找的。​​p>

    代码

    请注意我使用了椭圆而不是圆形,因为它包含在内。

    我的包裹:

    from __future__ import annotations
    
    from typing import Union, Tuple
    from shapely.geometry import Point, Polygon, box
    from shapely.affinity import scale, rotate
    from matplotlib.patches import Ellipse
    import numpy as np
    
    
    class Pixel:
        def __init__(self, x: int, y: int) -> None:
            """
            Creates a 1x1 box object on the given coordinates
            :param x: int
                    x coordinate
            :param y: int
                    y coordinate
            """
            self.x = x
            self.y = y
            self.body = self.__generate()
    
        def __str__(self) -> str:
            return f"Pixel(x={self.x}, y={self.y})"
    
        def __repr__(self) -> str:
            return self.__str__()
    
        def __generate(self) -> Polygon:
            """returns a 1x1 box on self.x, self.y"""
            return box(minx=self.x, miny=self.y, maxx=self.x + 1, maxy=self.y + 1)
    
    
    class EllipticalMask:
        def __init__(self, center: Tuple[Union[float, int], Union[float, int]],
                     a: Union[float, int], b: Union[float, int], angle: Union[float, int] = 0) -> None:
            """
            Creates an ellipse object on the given coordinates and is able to calculate a mask with given pixels.
            :param center: tuple
                    (x, y) coordinates
            :param a: float or int
                    sami-major axis of ellipse
            :param b:  float or int
                    sami-minor axis of ellipse
            :param angle:  float or int
                    angle of ellipse (counterclockwise)
            """
            self.center = center
            self.a = a
            self.b = b
            self.angle = angle
    
            self.body = self.__generate()
    
        def __generate(self) -> Polygon:
            """Returns an ellipse with given parameters"""
            return rotate(
                scale(
                    Point(self.center[1], self.center[0]).buffer(1),
                    self.a,
                    self.b
                ),
                self.angle
            )
    
        def __extreme_points(self) -> dict:
            """Finds extreme points which the polygon lying in"""
            x, y = self.body.exterior.coords.xy
            return {
                "X": {
                    "MIN": np.floor(min(x)), "MAX": np.ceil(max(x))
                },
                "Y": {
                    "MIN": np.floor(min(y)), "MAX": np.ceil(max(y))
                }
            }
    
        def __intersepter_pixels(self) -> list:
            """Creates a list of pixel objects which ellipse is covering"""
            points = self.__extreme_points()
            return [
                Pixel(x, y)
                for x in np.arange(points["X"]["MIN"], points["X"]["MAX"] + 1).astype(int)
                for y in np.arange(points["Y"]["MIN"], points["Y"]["MAX"] + 1).astype(int)
                if x >= 0 and y >= 0
            ]
    
        def mask(self, shape: tuple) -> np.ndarray:
            """
            Returns a float mask
            :param shape: tuple
                    the shape of the mask as (width, height)
            :return: ndarray
            """
            pixels = self.__intersepter_pixels()
            mask = np.zeros(shape).astype(float)
            for pixel in pixels:
                ratio = pixel.body.intersection(self.body).area
                mask[pixel.x][pixel.y] = ratio
    
            return mask
    
        def matplotlib_artist(self) -> Ellipse:
            """
            Returns a matplotlib artist
            :return: Ellipse
            """
            e = Ellipse(xy=(self.center[0] - 0.5, self.center[1] - 0.5), width=2 * self.a, height=2 * self.b,
                        angle=90 - self.angle)
            e.set_facecolor('none')
            e.set_edgecolor("red")
            return e
    
    
    class CircularMask(EllipticalMask):
        def __init__(self, center: Tuple[Union[float, int], Union[float, int]],
                     r: Union[float, int]) -> None:
            """
            Uses ellipse to create a circle
            :param center:  tuple
                    (x, y) coordinates
            :param r: float or int
                    radius of circle
            """
            super(CircularMask, self).__init__(center, r, r, 0)
    

    用法:

    from myLib import EllipticalMask
    from matplotlib import pyplot as plt
    
    m = EllipticalMask((50, 50), 25, 15, 20)
    
    mask = m.mask((100, 100))
    e = m.matplotlib_artist()
    
    fig, ax = plt.subplots(1, 1, figsize=(4, 4))
    ax.imshow(mask)
    ax.add_artist(e)
    plt.show()
    

    结果:

    感谢任何反馈。

    【讨论】:

      【解决方案2】:

      如果您不追求最​​高精度,一个简单的方法是计算像素中心到圆的有符号距离(即到中心的距离减去半径)。那么如果距离大于1,则认为像素完全在外面;如果小于 0,则完全在里面。当然,在这两者之间,距离会告诉您 0 到 100% 之间的分数。

      一种更准确的技术是计算像素四个角的距离,以便找到交叉的两条边(端点之间的符号变化)。这允许您构建一个多边形(三角形、四边形或五边形,当四边交叉时特别是六边形)并通过鞋带公式计算其面积。这很容易管理。

      为了得到准确的结果,您需要在多边形的斜边和圆之间添加圆弧的面积 (https://en.wikipedia.org/wiki/Circular_segment)。请注意,存在困难的极端情况。

      【讨论】:

        【解决方案3】:

        在线查找 Xiaolin Wu 的抗锯齿线条算法的修改形式,该算法为圆形渲染执行抗锯齿。

        虽然您没有在此处进行任何抗锯齿处理,但您可以轻松地使用此算法计算的 alpha 值作为对您的一组像素的弧覆盖率的替代度量。

        请注意,您只需要考虑该集合的子集,即圆所在的子集。

        【讨论】:

          【解决方案4】:

          我怀疑没有一个简单的公式可以给你这个问题的确切答案。如果您需要近似,您可以执行以下操作:

          查看所有正方形的角:

          1. 如果四个角都在圆内,则正方形在圆内
          2. 如果四个角都在圆外,则正方形在圆外。
          3. 否则,将正方形分成四个相等的子正方形并递归

          尽可能深入地递归以获得所需的任何准确性。

          我希望有人有更好的解决方案。

          【讨论】:

          • 额外的工作将大致与最小子正方形边长上的弧总长度成正比,而每个细分的精度翻四倍。
          • 除非用户非常不走运,否则我希望他们只需要递归下一个子方格。但是,是的,这并不是一个很好的算法。
          • 平均来说,大概是两个。
          猜你喜欢
          • 2015-03-21
          • 1970-01-01
          • 1970-01-01
          • 2013-11-23
          • 1970-01-01
          • 2016-07-28
          • 1970-01-01
          • 1970-01-01
          • 2011-09-24
          相关资源
          最近更新 更多