边界有比例的圆形蒙版
Circular mask with ratio on boundaries
我知道我必须付出一些努力,但我真的不知道如何解决这个问题。
我知道如何制作圆形蒙版:
在上面 link 的示例中,掩码是一个布尔数组。意味着它显示像素是否在我想称之为离散的圆圈下方。
但是我想知道每个像素在圆圈下面有多少。所以基本上我会有一个浮动蒙版数组,在圆的边界我会有一个值显示圆下有多少像素(百分比)。我想称之为连续的。
Please note, the numbers given are not calculated and are eyeball
estimate.
如果有人能指出正确的方向,我将不胜感激。
我怀疑没有一个简单的公式可以为您提供此问题的准确答案。如果你需要近似,你可以做类似下面的事情:
查看正方形的所有角:
- 如果四个角都在圆内,则正方形在圆内
- 如果四个角都在圆外,则正方形在圆外。
- 否则,将正方形分成四个相等的子正方形并递归
尽可能深入地递归以获得所需的任何准确性。
我希望有人有更好的解决方案。
如果您不追求最高准确度,一个简单的方法是计算像素中心到圆的带符号距离(即到中心的距离减去半径)。那么如果距离大于1,则认为该像素完全在外面;如果小于 0,则完全在里面。当然,在两者之间,距离会告诉您 0 到 100% 之间的分数。
一种更准确的技术是计算像素四个角的距离,以便找到交叉的两条边(端点之间的符号变化)。这允许您构造一个多边形(三角形、四边形或五边形,当四个边交叉时特别是六边形)并通过鞋带公式计算其面积。这很容易管理。
为了得到准确的结果,您需要加上多边形斜边和圆之间的圆弧段的面积(https://en.wikipedia.org/wiki/Circular_segment)。请注意,存在困难的极端情况。
网上找吴小林抗锯齿线算法的修改形式,对圆形渲染进行抗锯齿。
虽然您在这里没有进行任何抗锯齿处理,但您可以轻松地使用此算法计算出的 alpha 值作为您的像素集上弧覆盖的替代度量。
请注意,您只需考虑该集合的子集,圆弧段落在该子集上。
我要感谢你们所有人。特别是 @Yves Daoust 你给我指出了正确的方向。我想与面临类似问题的人分享我的解决方案:
我的解决方案使用了intersections
。如果我能找到矩形和圆形之间的交集区域,我也许可以将像素视为矩形,而圆形显然就是圆形。
两个形状之间的交集:
为此你可以使用 shapely:
要创建一个匀称的圆使用 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)
可以计算两个多边形的交集,它会 return 一个多边形:
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]
,这就是我们要寻找的值。
代码
请注意我使用了椭圆而不是圆,因为它包含在内。
我的包裹:
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()
结果:
欢迎任何反馈。
我知道我必须付出一些努力,但我真的不知道如何解决这个问题。
我知道如何制作圆形蒙版:
在上面 link 的示例中,掩码是一个布尔数组。意味着它显示像素是否在我想称之为离散的圆圈下方。
但是我想知道每个像素在圆圈下面有多少。所以基本上我会有一个浮动蒙版数组,在圆的边界我会有一个值显示圆下有多少像素(百分比)。我想称之为连续的。
Please note, the numbers given are not calculated and are eyeball estimate.
如果有人能指出正确的方向,我将不胜感激。
我怀疑没有一个简单的公式可以为您提供此问题的准确答案。如果你需要近似,你可以做类似下面的事情:
查看正方形的所有角:
- 如果四个角都在圆内,则正方形在圆内
- 如果四个角都在圆外,则正方形在圆外。
- 否则,将正方形分成四个相等的子正方形并递归
尽可能深入地递归以获得所需的任何准确性。
我希望有人有更好的解决方案。
如果您不追求最高准确度,一个简单的方法是计算像素中心到圆的带符号距离(即到中心的距离减去半径)。那么如果距离大于1,则认为该像素完全在外面;如果小于 0,则完全在里面。当然,在两者之间,距离会告诉您 0 到 100% 之间的分数。
一种更准确的技术是计算像素四个角的距离,以便找到交叉的两条边(端点之间的符号变化)。这允许您构造一个多边形(三角形、四边形或五边形,当四个边交叉时特别是六边形)并通过鞋带公式计算其面积。这很容易管理。
为了得到准确的结果,您需要加上多边形斜边和圆之间的圆弧段的面积(https://en.wikipedia.org/wiki/Circular_segment)。请注意,存在困难的极端情况。
网上找吴小林抗锯齿线算法的修改形式,对圆形渲染进行抗锯齿。
虽然您在这里没有进行任何抗锯齿处理,但您可以轻松地使用此算法计算出的 alpha 值作为您的像素集上弧覆盖的替代度量。
请注意,您只需考虑该集合的子集,圆弧段落在该子集上。
我要感谢你们所有人。特别是 @Yves Daoust 你给我指出了正确的方向。我想与面临类似问题的人分享我的解决方案:
我的解决方案使用了intersections
。如果我能找到矩形和圆形之间的交集区域,我也许可以将像素视为矩形,而圆形显然就是圆形。
两个形状之间的交集:
为此你可以使用 shapely:
要创建一个匀称的圆使用 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)
可以计算两个多边形的交集,它会 return 一个多边形:
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]
,这就是我们要寻找的值。
代码
请注意我使用了椭圆而不是圆,因为它包含在内。
我的包裹:
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()
结果:
欢迎任何反馈。