在skimage中绘制渐变椭圆
Draw a gradual change ellipse in skimage
我想在skimage中画一个渐变颜色的椭圆遮罩。颜色变化从椭圆内开始到椭圆外结束。如何用skimage或open-cv画出来?
如下图:
简介
让我们从详细描述示例图像开始。
- 这是一个 4 通道图像(RGB + alpha 透明度),但它只使用灰色阴影。
- 图像与绘图非常吻合,形状周围只有最小的边距。
- 有一个填充的、抗锯齿的、旋转的外椭圆,周围是透明背景。
- 外层椭圆填充旋转椭圆渐变(同心,与椭圆旋转相同),为黑色在边缘,线性前进到中心的白色。
- 外椭圆覆盖了同心、填充、消除锯齿、旋转的内椭圆(同样旋转,两个轴按相同比例缩放)。填充颜色为白色。
此外,让:
a
和b
是椭圆的半长轴和半短轴
theta
是椭圆围绕其中心的旋转角度(以弧度为单位(在 x 轴上的 0 a
点处)
inner_scale
是 inner 与 outer 椭圆的对应轴之间的比率(即 0.5 表示内部被缩放下降了 50%)
h
和k
是椭圆中心的(x,y)坐标
在演示代码中,我们将使用以下导入:
import cv2
import numpy as np
import math
并将定义绘图的参数(我们将计算 h
和 k
)设置为:
a, b = (360.0, 200.0) # Semi-major and semi-minor axis
theta = math.radians(40.0) # Ellipse rotation (radians)
inner_scale = 0.6 # Scale of the inner full-white ellipse
第 1 步
为了生成这样的图像,我们需要采取的第一步是计算我们需要的“canvas”(我们将绘制的图像)的大小。为此,我们可以计算旋转后的外椭圆的边界框,并在其周围添加一些小边距。
我不知道现有的 OpenCV 函数可以有效地执行此操作,但 Whosebug 挽救了这一天——已经有一个相关的 question with an answer linking to a useful article 讨论了这个问题。我们可以使用这些资源提出以下 Python 实现:
def ellipse_bbox(h, k, a, b, theta):
ux = a * math.cos(theta)
uy = a * math.sin(theta)
vx = b * math.cos(theta + math.pi / 2)
vy = b * math.sin(theta + math.pi / 2)
box_halfwidth = np.ceil(math.sqrt(ux**2 + vx**2))
box_halfheight = np.ceil(math.sqrt(uy**2 + vy**2))
return ((int(h - box_halfwidth), int(k - box_halfheight))
, (int(h + box_halfwidth), int(k + box_halfheight)))
NB: 我将浮点数四舍五入,因为我们必须覆盖整个像素,return 左上角和右下角为整数 (x, y) 对。
然后我们可以按以下方式使用该函数:
# Calculate the image size needed to draw this and center the ellipse
_, (h, k) = ellipse_bbox(0, 0, a, b, theta) # Ellipse center
h += 2 # Add small margin
k += 2 # Add small margin
width, height = (h*2+1, k*2+1) # Canvas size
第 2 步
第二步是生成透明层。这是一个单通道 8 位图像,其中黑色 (0) 代表完全透明,白色 (255) 代表完全不透明像素。这个任务相当简单,因为我们可以使用 cv2.ellipse
.
我们可以定义 外椭圆 形式 RotatedRect
结构(一个旋转的矩形与椭圆紧密匹配)。在 Python 中,这表示为包含以下内容的元组:
- 表示旋转矩形中心的元组(x 和 y 坐标)
- 表示旋转矩形大小(宽度和高度)的元组
- 以度为单位的旋转角度
代码如下:
ellipse_outer = ((h,k), (a*2, b*2), math.degrees(theta))
transparency = np.zeros((height, width), np.uint8)
cv2.ellipse(transparency, ellipse_outer, 255, -1, cv2.LINE_AA)
...及其生成的图像:
步骤 #3
作为第三步,我们创建了一个包含我们想要的旋转椭圆梯度 的单通道(灰度或强度)图像。但首先,我们如何使用 (a, b)
、theta
(θ) 和 (h, k)
根据图像的笛卡尔 (x, y)
坐标在数学上定义旋转的椭圆参数?
这次 Mathematics StackExchange 拯救了这一天:有一个 question exactly matching our problem, and an answer 提供了这个有用的方程式:
请注意,对于我们从椭圆中心出发的任何方向,左侧在椭圆周长处的计算结果为 1。在中心为0,向周边的1线性增加,然后越过它
我们称右边为weight
,因为找不到更好的术语。由于它从中心向外很好地缩放,我们可以用它来计算我们想要的梯度。我们的公式在外部给出白色(对于浮点图像为 1.0),在中心给出黑色(0.0)。我们想要倒数,所以我们只需从 1.0
中减去 weight
并将结果限制在 [0.0, 1.0]
.
范围内
让我们从一个简单的仅 Python 实现开始(如手动迭代代表我们图像的 numpy.array
的各个元素)来计算权重。然而,由于我们是懒惰的程序员,我们将使用 Numpy 将计算出的 weight
s 转换为分级图像,使用矢量化减法,以及 numpy.clip
.
代码如下:
def make_gradient_v1(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
weights = np.zeros((height, width), np.float64)
for y in range(height):
for x in range(width):
weights[y,x] = ((((x-h) * ct + (y-k) * st) ** 2) / aa
+ (((x-h) * st - (y-k) * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
...及其生成的图像:
这一切都很好,但由于我们遍历每个像素并在 Python 解释器中进行计算,它也是 aaawwwfffuuulllllllyyy ssslllooowww.... 可能需要一秒钟,但我们使用的是 Numpy ,所以如果我们利用它,我们肯定可以做得更好。这意味着我们可以矢量化任何东西。
首先,让我们注意到唯一变化的输入是每个给定像素的坐标。这意味着要向量化我们的算法,我们需要两个数组(与图像大小相同)作为输入,保存每个像素的 x
和 y
坐标。幸运的是,Numpy 为我们提供了生成此类数组的工具——numpy.mgrid
。我们可以写
y,x = np.mgrid[:height,:width]
生成我们需要的输入数组。然而,让我们观察一下,我们从不直接使用 x
和 y
——而是我们总是用一个常量来抵消它们。让我们通过生成 x-h
和 y-k
...
来避免偏移操作
y,x = np.mgrid[-k:height-k,-h:width-h]
我们可以再次预先计算 4 个常量,除此之外,其余的只是矢量化 addition, subtraction, multiplication, division and powers,它们都是由 Numpy 作为矢量化运算提供的(即更快)。
def make_gradient_v2(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
# Generate (x,y) coordinate arrays
y,x = np.mgrid[-k:height-k,-h:width-h]
# Calculate the weight for each pixel
weights = (((x * ct + y * st) ** 2) / aa) + (((x * st - y * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
与 Python-only 相比,使用此版本的脚本大约需要 30% 的时间。没什么了不起的,但它产生了相同的结果,而且这个任务似乎不需要经常做,所以对我来说已经足够了。
如果您[reader]知道更快的方法,请post将其作为答案。
现在我们得到了一个浮点图像,其中强度范围在 0.0 到 1.0 之间。为了生成我们的结果,我们需要一个 8 位图像,其值介于 0 和 255 之间。
intensity = np.uint8(make_gradient_v2(width, height, h, k, a, b, theta) * 255)
第 4 步
第四步——绘制内椭圆。这很简单,我们以前做过。我们只需要适当地缩放轴。
ellipse_inner = ((h,k), (a*2*inner_scale, b*2*inner_scale), math.degrees(theta))
cv2.ellipse(intensity, ellipse_inner, 255, -1, cv2.LINE_AA)
这给了我们以下强度图像:
第 5 步
第五步 -- 我们就快完成了。我们所要做的就是将强度层和透明度层组合成 BGRA 图像,然后将其另存为 PNG。
result = cv2.merge([intensity, intensity, intensity, transparency])
注意: 对红色、绿色和蓝色使用相同的强度只能得到灰色阴影。
当我们保存结果时,我们得到如下图像:
结论
鉴于我已经估计了您用于生成示例图像的参数,我想说我的脚本的结果非常接近。它的运行速度也相当快——如果您想要更好的东西,您可能无法避免接近裸机(C、C++ 等)。一种更聪明的方法,或者 GPU 或许可以做得更好。值得尝试...
总而言之,这里有一个小演示,表明此代码也适用于其他旋转:
以及我用来写这个的完整脚本:
import cv2
import numpy as np
import math
# ============================================================================
def ellipse_bbox(h, k, a, b, theta):
ux = a * math.cos(theta)
uy = a * math.sin(theta)
vx = b * math.cos(theta + math.pi / 2)
vy = b * math.sin(theta + math.pi / 2)
box_halfwidth = np.ceil(math.sqrt(ux**2 + vx**2))
box_halfheight = np.ceil(math.sqrt(uy**2 + vy**2))
return ((int(h - box_halfwidth), int(k - box_halfheight))
, (int(h + box_halfwidth), int(k + box_halfheight)))
# ----------------------------------------------------------------------------
# Rotated elliptical gradient - slow, Python-only approach
def make_gradient_v1(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
weights = np.zeros((height, width), np.float64)
for y in range(height):
for x in range(width):
weights[y,x] = ((((x-h) * ct + (y-k) * st) ** 2) / aa
+ (((x-h) * st - (y-k) * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
# ----------------------------------------------------------------------------
# Rotated elliptical gradient - faster, vectorized numpy approach
def make_gradient_v2(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
# Generate (x,y) coordinate arrays
y,x = np.mgrid[-k:height-k,-h:width-h]
# Calculate the weight for each pixel
weights = (((x * ct + y * st) ** 2) / aa) + (((x * st - y * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
# ============================================================================
def draw_image(a, b, theta, inner_scale, save_intermediate=False):
# Calculate the image size needed to draw this and center the ellipse
_, (h, k) = ellipse_bbox(0,0,a,b,theta) # Ellipse center
h += 2 # Add small margin
k += 2 # Add small margin
width, height = (h*2+1, k*2+1) # Canvas size
# Parameters defining the two ellipses for OpenCV (a RotatedRect structure)
ellipse_outer = ((h,k), (a*2, b*2), math.degrees(theta))
ellipse_inner = ((h,k), (a*2*inner_scale, b*2*inner_scale), math.degrees(theta))
# Generate the transparency layer -- the outer ellipse filled and anti-aliased
transparency = np.zeros((height, width), np.uint8)
cv2.ellipse(transparency, ellipse_outer, 255, -1, cv2.LINE_AA)
if save_intermediate:
cv2.imwrite("eligrad-t.png", transparency) # Save intermediate for demo
# Generate the gradient and scale it to 8bit grayscale range
intensity = np.uint8(make_gradient_v1(width, height, h, k, a, b, theta) * 255)
if save_intermediate:
cv2.imwrite("eligrad-i1.png", intensity) # Save intermediate for demo
# Draw the inter ellipse filled and anti-aliased
cv2.ellipse(intensity, ellipse_inner, 255, -1, cv2.LINE_AA)
if save_intermediate:
cv2.imwrite("eligrad-i2.png", intensity) # Save intermediate for demo
# Turn it into a BGRA image
result = cv2.merge([intensity, intensity, intensity, transparency])
return result
# ============================================================================
a, b = (360.0, 200.0) # Semi-major and semi-minor axis
theta = math.radians(40.0) # Ellipse rotation (radians)
inner_scale = 0.6 # Scale of the inner full-white ellipse
cv2.imwrite("eligrad.png", draw_image(a, b, theta, inner_scale, True))
# ============================================================================
rows = []
for j in range(0, 4, 1):
cols = []
for i in range(0, 90, 10):
tile = np.zeros((170, 170, 4), np.uint8)
image = draw_image(80.0, 50.0, math.radians(i + j * 90), 0.6)
tile[:image.shape[0],:image.shape[1]] = image
cols.append(tile)
rows.append(np.hstack(cols))
cv2.imwrite("eligrad-m.png", np.vstack(rows))
注意:如果您发现任何愚蠢的错误、令人困惑的术语或任何其他与此 post 相关的问题,请随时发表建设性评论,或者直接编辑答案以使其更好。我知道有一些方法可以进一步优化它——让它由 reader 来做这个练习(并可能提供和免费的答案)。
我想在skimage中画一个渐变颜色的椭圆遮罩。颜色变化从椭圆内开始到椭圆外结束。如何用skimage或open-cv画出来?
如下图:
简介
让我们从详细描述示例图像开始。
- 这是一个 4 通道图像(RGB + alpha 透明度),但它只使用灰色阴影。
- 图像与绘图非常吻合,形状周围只有最小的边距。
- 有一个填充的、抗锯齿的、旋转的外椭圆,周围是透明背景。
- 外层椭圆填充旋转椭圆渐变(同心,与椭圆旋转相同),为黑色在边缘,线性前进到中心的白色。
- 外椭圆覆盖了同心、填充、消除锯齿、旋转的内椭圆(同样旋转,两个轴按相同比例缩放)。填充颜色为白色。
此外,让:
a
和b
是椭圆的半长轴和半短轴theta
是椭圆围绕其中心的旋转角度(以弧度为单位(在 x 轴上的 0a
点处)inner_scale
是 inner 与 outer 椭圆的对应轴之间的比率(即 0.5 表示内部被缩放下降了 50%)h
和k
是椭圆中心的(x,y)坐标
在演示代码中,我们将使用以下导入:
import cv2
import numpy as np
import math
并将定义绘图的参数(我们将计算 h
和 k
)设置为:
a, b = (360.0, 200.0) # Semi-major and semi-minor axis
theta = math.radians(40.0) # Ellipse rotation (radians)
inner_scale = 0.6 # Scale of the inner full-white ellipse
第 1 步
为了生成这样的图像,我们需要采取的第一步是计算我们需要的“canvas”(我们将绘制的图像)的大小。为此,我们可以计算旋转后的外椭圆的边界框,并在其周围添加一些小边距。
我不知道现有的 OpenCV 函数可以有效地执行此操作,但 Whosebug 挽救了这一天——已经有一个相关的 question with an answer linking to a useful article 讨论了这个问题。我们可以使用这些资源提出以下 Python 实现:
def ellipse_bbox(h, k, a, b, theta):
ux = a * math.cos(theta)
uy = a * math.sin(theta)
vx = b * math.cos(theta + math.pi / 2)
vy = b * math.sin(theta + math.pi / 2)
box_halfwidth = np.ceil(math.sqrt(ux**2 + vx**2))
box_halfheight = np.ceil(math.sqrt(uy**2 + vy**2))
return ((int(h - box_halfwidth), int(k - box_halfheight))
, (int(h + box_halfwidth), int(k + box_halfheight)))
NB: 我将浮点数四舍五入,因为我们必须覆盖整个像素,return 左上角和右下角为整数 (x, y) 对。
然后我们可以按以下方式使用该函数:
# Calculate the image size needed to draw this and center the ellipse
_, (h, k) = ellipse_bbox(0, 0, a, b, theta) # Ellipse center
h += 2 # Add small margin
k += 2 # Add small margin
width, height = (h*2+1, k*2+1) # Canvas size
第 2 步
第二步是生成透明层。这是一个单通道 8 位图像,其中黑色 (0) 代表完全透明,白色 (255) 代表完全不透明像素。这个任务相当简单,因为我们可以使用 cv2.ellipse
.
我们可以定义 外椭圆 形式 RotatedRect
结构(一个旋转的矩形与椭圆紧密匹配)。在 Python 中,这表示为包含以下内容的元组:
- 表示旋转矩形中心的元组(x 和 y 坐标)
- 表示旋转矩形大小(宽度和高度)的元组
- 以度为单位的旋转角度
代码如下:
ellipse_outer = ((h,k), (a*2, b*2), math.degrees(theta))
transparency = np.zeros((height, width), np.uint8)
cv2.ellipse(transparency, ellipse_outer, 255, -1, cv2.LINE_AA)
...及其生成的图像:
步骤 #3
作为第三步,我们创建了一个包含我们想要的旋转椭圆梯度 的单通道(灰度或强度)图像。但首先,我们如何使用 (a, b)
、theta
(θ) 和 (h, k)
根据图像的笛卡尔 (x, y)
坐标在数学上定义旋转的椭圆参数?
这次 Mathematics StackExchange 拯救了这一天:有一个 question exactly matching our problem, and an answer 提供了这个有用的方程式:
请注意,对于我们从椭圆中心出发的任何方向,左侧在椭圆周长处的计算结果为 1。在中心为0,向周边的1线性增加,然后越过它
我们称右边为weight
,因为找不到更好的术语。由于它从中心向外很好地缩放,我们可以用它来计算我们想要的梯度。我们的公式在外部给出白色(对于浮点图像为 1.0),在中心给出黑色(0.0)。我们想要倒数,所以我们只需从 1.0
中减去 weight
并将结果限制在 [0.0, 1.0]
.
让我们从一个简单的仅 Python 实现开始(如手动迭代代表我们图像的 numpy.array
的各个元素)来计算权重。然而,由于我们是懒惰的程序员,我们将使用 Numpy 将计算出的 weight
s 转换为分级图像,使用矢量化减法,以及 numpy.clip
.
代码如下:
def make_gradient_v1(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
weights = np.zeros((height, width), np.float64)
for y in range(height):
for x in range(width):
weights[y,x] = ((((x-h) * ct + (y-k) * st) ** 2) / aa
+ (((x-h) * st - (y-k) * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
...及其生成的图像:
这一切都很好,但由于我们遍历每个像素并在 Python 解释器中进行计算,它也是 aaawwwfffuuulllllllyyy ssslllooowww.... 可能需要一秒钟,但我们使用的是 Numpy ,所以如果我们利用它,我们肯定可以做得更好。这意味着我们可以矢量化任何东西。
首先,让我们注意到唯一变化的输入是每个给定像素的坐标。这意味着要向量化我们的算法,我们需要两个数组(与图像大小相同)作为输入,保存每个像素的 x
和 y
坐标。幸运的是,Numpy 为我们提供了生成此类数组的工具——numpy.mgrid
。我们可以写
y,x = np.mgrid[:height,:width]
生成我们需要的输入数组。然而,让我们观察一下,我们从不直接使用 x
和 y
——而是我们总是用一个常量来抵消它们。让我们通过生成 x-h
和 y-k
...
y,x = np.mgrid[-k:height-k,-h:width-h]
我们可以再次预先计算 4 个常量,除此之外,其余的只是矢量化 addition, subtraction, multiplication, division and powers,它们都是由 Numpy 作为矢量化运算提供的(即更快)。
def make_gradient_v2(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
# Generate (x,y) coordinate arrays
y,x = np.mgrid[-k:height-k,-h:width-h]
# Calculate the weight for each pixel
weights = (((x * ct + y * st) ** 2) / aa) + (((x * st - y * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
与 Python-only 相比,使用此版本的脚本大约需要 30% 的时间。没什么了不起的,但它产生了相同的结果,而且这个任务似乎不需要经常做,所以对我来说已经足够了。
如果您[reader]知道更快的方法,请post将其作为答案。
现在我们得到了一个浮点图像,其中强度范围在 0.0 到 1.0 之间。为了生成我们的结果,我们需要一个 8 位图像,其值介于 0 和 255 之间。
intensity = np.uint8(make_gradient_v2(width, height, h, k, a, b, theta) * 255)
第 4 步
第四步——绘制内椭圆。这很简单,我们以前做过。我们只需要适当地缩放轴。
ellipse_inner = ((h,k), (a*2*inner_scale, b*2*inner_scale), math.degrees(theta))
cv2.ellipse(intensity, ellipse_inner, 255, -1, cv2.LINE_AA)
这给了我们以下强度图像:
第 5 步
第五步 -- 我们就快完成了。我们所要做的就是将强度层和透明度层组合成 BGRA 图像,然后将其另存为 PNG。
result = cv2.merge([intensity, intensity, intensity, transparency])
注意: 对红色、绿色和蓝色使用相同的强度只能得到灰色阴影。
当我们保存结果时,我们得到如下图像:
结论
鉴于我已经估计了您用于生成示例图像的参数,我想说我的脚本的结果非常接近。它的运行速度也相当快——如果您想要更好的东西,您可能无法避免接近裸机(C、C++ 等)。一种更聪明的方法,或者 GPU 或许可以做得更好。值得尝试...
总而言之,这里有一个小演示,表明此代码也适用于其他旋转:
以及我用来写这个的完整脚本:
import cv2
import numpy as np
import math
# ============================================================================
def ellipse_bbox(h, k, a, b, theta):
ux = a * math.cos(theta)
uy = a * math.sin(theta)
vx = b * math.cos(theta + math.pi / 2)
vy = b * math.sin(theta + math.pi / 2)
box_halfwidth = np.ceil(math.sqrt(ux**2 + vx**2))
box_halfheight = np.ceil(math.sqrt(uy**2 + vy**2))
return ((int(h - box_halfwidth), int(k - box_halfheight))
, (int(h + box_halfwidth), int(k + box_halfheight)))
# ----------------------------------------------------------------------------
# Rotated elliptical gradient - slow, Python-only approach
def make_gradient_v1(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
weights = np.zeros((height, width), np.float64)
for y in range(height):
for x in range(width):
weights[y,x] = ((((x-h) * ct + (y-k) * st) ** 2) / aa
+ (((x-h) * st - (y-k) * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
# ----------------------------------------------------------------------------
# Rotated elliptical gradient - faster, vectorized numpy approach
def make_gradient_v2(width, height, h, k, a, b, theta):
# Precalculate constants
st, ct = math.sin(theta), math.cos(theta)
aa, bb = a**2, b**2
# Generate (x,y) coordinate arrays
y,x = np.mgrid[-k:height-k,-h:width-h]
# Calculate the weight for each pixel
weights = (((x * ct + y * st) ** 2) / aa) + (((x * st - y * ct) ** 2) / bb)
return np.clip(1.0 - weights, 0, 1)
# ============================================================================
def draw_image(a, b, theta, inner_scale, save_intermediate=False):
# Calculate the image size needed to draw this and center the ellipse
_, (h, k) = ellipse_bbox(0,0,a,b,theta) # Ellipse center
h += 2 # Add small margin
k += 2 # Add small margin
width, height = (h*2+1, k*2+1) # Canvas size
# Parameters defining the two ellipses for OpenCV (a RotatedRect structure)
ellipse_outer = ((h,k), (a*2, b*2), math.degrees(theta))
ellipse_inner = ((h,k), (a*2*inner_scale, b*2*inner_scale), math.degrees(theta))
# Generate the transparency layer -- the outer ellipse filled and anti-aliased
transparency = np.zeros((height, width), np.uint8)
cv2.ellipse(transparency, ellipse_outer, 255, -1, cv2.LINE_AA)
if save_intermediate:
cv2.imwrite("eligrad-t.png", transparency) # Save intermediate for demo
# Generate the gradient and scale it to 8bit grayscale range
intensity = np.uint8(make_gradient_v1(width, height, h, k, a, b, theta) * 255)
if save_intermediate:
cv2.imwrite("eligrad-i1.png", intensity) # Save intermediate for demo
# Draw the inter ellipse filled and anti-aliased
cv2.ellipse(intensity, ellipse_inner, 255, -1, cv2.LINE_AA)
if save_intermediate:
cv2.imwrite("eligrad-i2.png", intensity) # Save intermediate for demo
# Turn it into a BGRA image
result = cv2.merge([intensity, intensity, intensity, transparency])
return result
# ============================================================================
a, b = (360.0, 200.0) # Semi-major and semi-minor axis
theta = math.radians(40.0) # Ellipse rotation (radians)
inner_scale = 0.6 # Scale of the inner full-white ellipse
cv2.imwrite("eligrad.png", draw_image(a, b, theta, inner_scale, True))
# ============================================================================
rows = []
for j in range(0, 4, 1):
cols = []
for i in range(0, 90, 10):
tile = np.zeros((170, 170, 4), np.uint8)
image = draw_image(80.0, 50.0, math.radians(i + j * 90), 0.6)
tile[:image.shape[0],:image.shape[1]] = image
cols.append(tile)
rows.append(np.hstack(cols))
cv2.imwrite("eligrad-m.png", np.vstack(rows))
注意:如果您发现任何愚蠢的错误、令人困惑的术语或任何其他与此 post 相关的问题,请随时发表建设性评论,或者直接编辑答案以使其更好。我知道有一些方法可以进一步优化它——让它由 reader 来做这个练习(并可能提供和免费的答案)。