Pygame [AI 汽车模型视觉] 中遮罩和发射光束之间的重叠
Overlap between mask and fired beams in Pygame [AI car model vision]
我尝试在 Pygame 中使用预定义的轨道掩码实现光束碰撞检测。我的最终目标是让 AI 汽车模型能够看到它所行驶的轨道:
这是我当前的代码,我在其中发射光束以遮蔽并尝试找到重叠:
import math
import sys
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pg.init()
beam_surface = pg.Surface((500, 500), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
# compute beam final point
x_dest = 250 + 500 * math.cos(math.radians(angle))
y_dest = 250 + 500 * math.sin(math.radians(angle))
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (250, 250), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
hit = mask.overlap(beam_mask, (pos[0] - 250, pos[1] - 250))
if hit is not None:
pg.draw.line(surface, BLUE, mouse_pos, hit)
pg.draw.circle(surface, GREEN, hit, 3)
surface = pg.display.set_mode((500, 500))
mask_surface = pg.image.load("../assets/mask.png")
mask = pg.mask.from_surface(mask_surface)
clock = pg.time.Clock()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
sys.exit()
mouse_pos = pg.mouse.get_pos()
surface.fill((0, 0, 0))
surface.blit(mask_surface, mask_surface.get_rect())
for angle in range(0, 120, 30):
draw_beam(surface, angle, mouse_pos)
pg.display.update()
clock.tick(30)
让我们描述一下代码片段中发生的事情。一个接一个,我将光束绘制到 beam_surface
,从中制作蒙版,并找到与由一个矩形和一个圆(gif 中的黑色)定义的背景蒙版重叠的部分。如果有 "hit point"(两个蒙版之间的重叠点),我会用一条连接点击点和鼠标位置的线来绘制它。
它 工作正常 角度 <0,90>
:
但是不工作范围内的角度<90,360>
:
Pygame 的 overlap()
文档告诉我们:
Starting at the top left corner it checks bits 0 to W - 1 of the first row ((0, 0) to (W - 1, 0)) then continues to the next row ((0, 1) to (W - 1, 1)). Once this entire column block is checked, it continues to the next one (W to 2 * W - 1).
这意味着这种方法只有在光束大约从左上角击中掩模时才有效。您对如何使其适用于所有情况有什么建议吗?这通常是解决此问题的好方法吗?
如果光线轴的 x 和 y 分量指向正方向,您的方法就可以正常工作,但如果它指向负方向,则失败。正如您所指出的,这是由 pygame.mask.Mask.overlap 的工作方式引起的:
Starting at the top left corner it checks bits 0 to W - 1 of the first row ((0, 0) to (W - 1, 0)) then continues to the next row ((0, 1) to (W - 1, 1)). Once this entire column block is checked, it continues to the next one (W to 2 * W - 1).
要使算法起作用,您必须确保光线始终指向正方向。因此,如果光线指向负 x 方向,则翻转蒙版和垂直光线,如果光线指向负 y 方向,则水平翻转光线。
使用pygame.transform.flip()
顶部创建4个面具。未翻转、水平翻转、垂直翻转以及垂直和水平翻转:
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
判断光线方向:
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
根据光线方向获取翻转掩码:
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
计算翻转的目标点:
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
计算翻转偏移量:
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
获取flipped光线与mask最近的交点和unflip交点:
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 500 - hit[0] if flip_x else hit[0]
hy = 500 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, mouse_pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
看例子: repl.it/@Rabbid76/PyGame-PyGame-SurfaceLineMaskIntersect-2
import math
import sys
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pg.init()
beam_surface = pg.Surface((500, 500), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (250, 250), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
#pg.draw.circle(surface, (255, 255, 0), mouse_pos, 3)
surface = pg.display.set_mode((500, 500))
#mask_surface = pg.image.load("../assets/mask.png")
mask_surface = pg.Surface((500, 500), pg.SRCALPHA)
mask_surface.fill((255, 0, 0))
pg.draw.circle(mask_surface, (0, 0, 0, 0), (250, 250), 100)
pg.draw.rect(mask_surface, (0, 0, 0, 0), (170, 170, 160, 160))
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
clock = pg.time.Clock()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
sys.exit()
mouse_pos = pg.mouse.get_pos()
surface.fill((0, 0, 0))
surface.blit(mask_surface, mask_surface.get_rect())
for angle in range(0, 359, 30):
draw_beam(surface, angle, mouse_pos)
pg.display.update()
clock.tick(30)
不是,算法可以进一步改进。射线始终绘制在 beam_surface
的右下象限。因此不再需要其他 3 个象限,beam_surface
的大小可以减少到 250x250。光线的起点是 (0, 0) 而不是 (250, 250) 并且必须稍微调整偏移量的计算:
beam_surface = pg.Surface((250, 250), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 500 * abs(c)
y_dest = 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (0, 0), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 499-pos[0] if flip_x else pos[0]
offset_y = 499-pos[1] if flip_y else pos[1]
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
我尝试在 Pygame 中使用预定义的轨道掩码实现光束碰撞检测。我的最终目标是让 AI 汽车模型能够看到它所行驶的轨道:
这是我当前的代码,我在其中发射光束以遮蔽并尝试找到重叠:
import math
import sys
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pg.init()
beam_surface = pg.Surface((500, 500), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
# compute beam final point
x_dest = 250 + 500 * math.cos(math.radians(angle))
y_dest = 250 + 500 * math.sin(math.radians(angle))
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (250, 250), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
hit = mask.overlap(beam_mask, (pos[0] - 250, pos[1] - 250))
if hit is not None:
pg.draw.line(surface, BLUE, mouse_pos, hit)
pg.draw.circle(surface, GREEN, hit, 3)
surface = pg.display.set_mode((500, 500))
mask_surface = pg.image.load("../assets/mask.png")
mask = pg.mask.from_surface(mask_surface)
clock = pg.time.Clock()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
sys.exit()
mouse_pos = pg.mouse.get_pos()
surface.fill((0, 0, 0))
surface.blit(mask_surface, mask_surface.get_rect())
for angle in range(0, 120, 30):
draw_beam(surface, angle, mouse_pos)
pg.display.update()
clock.tick(30)
让我们描述一下代码片段中发生的事情。一个接一个,我将光束绘制到 beam_surface
,从中制作蒙版,并找到与由一个矩形和一个圆(gif 中的黑色)定义的背景蒙版重叠的部分。如果有 "hit point"(两个蒙版之间的重叠点),我会用一条连接点击点和鼠标位置的线来绘制它。
它 工作正常 角度 <0,90>
:
但是不工作范围内的角度<90,360>
:
Pygame 的 overlap()
文档告诉我们:
Starting at the top left corner it checks bits 0 to W - 1 of the first row ((0, 0) to (W - 1, 0)) then continues to the next row ((0, 1) to (W - 1, 1)). Once this entire column block is checked, it continues to the next one (W to 2 * W - 1).
这意味着这种方法只有在光束大约从左上角击中掩模时才有效。您对如何使其适用于所有情况有什么建议吗?这通常是解决此问题的好方法吗?
如果光线轴的 x 和 y 分量指向正方向,您的方法就可以正常工作,但如果它指向负方向,则失败。正如您所指出的,这是由 pygame.mask.Mask.overlap 的工作方式引起的:
Starting at the top left corner it checks bits 0 to W - 1 of the first row ((0, 0) to (W - 1, 0)) then continues to the next row ((0, 1) to (W - 1, 1)). Once this entire column block is checked, it continues to the next one (W to 2 * W - 1).
要使算法起作用,您必须确保光线始终指向正方向。因此,如果光线指向负 x 方向,则翻转蒙版和垂直光线,如果光线指向负 y 方向,则水平翻转光线。
使用pygame.transform.flip()
顶部创建4个面具。未翻转、水平翻转、垂直翻转以及垂直和水平翻转:
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
判断光线方向:
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
根据光线方向获取翻转掩码:
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
计算翻转的目标点:
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
计算翻转偏移量:
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
获取flipped光线与mask最近的交点和unflip交点:
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 500 - hit[0] if flip_x else hit[0]
hy = 500 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, mouse_pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
看例子:
import math
import sys
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pg.init()
beam_surface = pg.Surface((500, 500), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (250, 250), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
#pg.draw.circle(surface, (255, 255, 0), mouse_pos, 3)
surface = pg.display.set_mode((500, 500))
#mask_surface = pg.image.load("../assets/mask.png")
mask_surface = pg.Surface((500, 500), pg.SRCALPHA)
mask_surface.fill((255, 0, 0))
pg.draw.circle(mask_surface, (0, 0, 0, 0), (250, 250), 100)
pg.draw.rect(mask_surface, (0, 0, 0, 0), (170, 170, 160, 160))
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
clock = pg.time.Clock()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
sys.exit()
mouse_pos = pg.mouse.get_pos()
surface.fill((0, 0, 0))
surface.blit(mask_surface, mask_surface.get_rect())
for angle in range(0, 359, 30):
draw_beam(surface, angle, mouse_pos)
pg.display.update()
clock.tick(30)
不是,算法可以进一步改进。射线始终绘制在 beam_surface
的右下象限。因此不再需要其他 3 个象限,beam_surface
的大小可以减少到 250x250。光线的起点是 (0, 0) 而不是 (250, 250) 并且必须稍微调整偏移量的计算:
beam_surface = pg.Surface((250, 250), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 500 * abs(c)
y_dest = 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (0, 0), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 499-pos[0] if flip_x else pos[0]
offset_y = 499-pos[1] if flip_y else pos[1]
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)