[英]How to convert screen x,y (cartesian coordinates) to 3D world space crosshair movement angles (screenToWorld)?
最近我一直在研究计算机视觉和神经网络。
并在 3D 应用程序中遇到了实验对象检测。
但是,令我惊讶的是 - 我遇到了将一个坐标系转换为另一个坐标系的问题(AFAIK 笛卡尔坐标系到极坐标/球体) 。
让我解释。
例如,我们有一个 3D 应用程序窗口的屏幕截图(一些 3D 游戏) :
现在,使用 Open-CV 或神经网络,我能够检测到圆形球体(游戏中的目标) 。
以及它们在游戏窗口内的X, Y
坐标(x、y 偏移量) 。
如果我将以编程方式在给定的X, Y
坐标内移动鼠标光标以瞄准其中一个目标。
只有当我在桌面环境中(在桌面上移动光标)时它才会起作用。
但是,当我切换到 3D 游戏时,我的鼠标光标现在位于 3D 游戏世界环境中 - 它不起作用并且不瞄准目标。
因此,我对该主题进行了不错的研究。
我遇到的是鼠标光标在 3D 游戏中被锁定。
因此,我们无法在mouse_event
win32 调用中使用MOUSEEVENTF_MOVE (0x0001) + MOUSEEVENTF_ABSOLUTE (0x8000)
标志移动光标。
我们只能使用相对移动以编程方式移动鼠标。
而且,从理论上讲,为了获得此相对鼠标移动偏移量,我们可以计算检测到 3D 游戏窗口中间的偏移量。
在这种情况下,如果目标点在屏幕100px
左侧 100 像素处,则相对运动矢量将类似于(x=-100, y=0)
。
问题是,3D 游戏中的十字准线不会像预期的那样向左移动100px
。
并且不会瞄准给定的目标。
但它会朝着给定的方向移动一点。
之后,我对该主题进行了更多研究。
据我了解,3D 游戏中的十字准线是使用 3D 空间中的角度移动的。
具体来说,只有两个: horizontal movement angles
和vertical movement angles
。
因此,游戏引擎获取我们的鼠标移动并将其转换为给定 3D 世界空间内的移动角度。
这就是 3D 游戏中十字准线移动的方式。
但是我们无权访问它,我们只能通过外部win32
调用移动鼠标。
然后我决定以某种方式计算pixels per degree
像素(我们需要使用win32
相对鼠标移动的像素数量,以便在游戏中将十字准线移动 1 度) 。
为了做到这一点,我写下了一个简单的计算算法。
这里是:
如您所见,我们需要将鼠标相对于win32
水平移动16400
像素,以便将游戏中的十字准线移动 360 度。
事实上,它有效。
16400/2
将十字准线分别移动 180 度。
我接下来要做的是尝试将我们的屏幕X, Y
目标偏移坐标转换为百分比(从屏幕中间开始)。
然后将它们转换为度数。
整体公式看起来像(仅水平移动的示例) :
w = 100 # screen width
x_offset = 10 # target x offset
hor_fov = 106.26
degs = (hor_fov/2) * (x_offset /w) # 5.313 degrees
事实上,它奏效了!
但并不完全像预期的那样。
总体瞄准精度不同,具体取决于目标距离屏幕中间的距离。
我不太擅长三角学,但正如我可以说的那样——这与极坐标/球坐标有关。
因为我们只能在水平和垂直方向上看到游戏世界的一部分。
它也被称为FOV (Field of view)
。
因此,在给定的 3D 游戏中,我们只能水平查看106.26
度。
垂直73.74
度。
我的猜测是,我正在尝试将坐标从线性系统转换为非线性系统。
结果,整体精度不够好。
我也试过在 Python 中使用math.atan
。
它有效,但仍然 - 不准确。
这是代码:
def point_get_difference(source_point, dest_point):
# 1000, 1000
# source_point = (960, 540)
# dest_point = (833, 645)
# result = (100, 100)
x = dest_point[0]-source_point[0]
y = dest_point[1]-source_point[1]
return x, y
def get_move_angle__new(aim_target, gwr, pixels_per_degree, fov):
game_window_rect__center = (gwr[2]/2, gwr[3]/2)
rel_diff = list(point_get_difference(game_window_rect__center, aim_target))
x_degs = degrees(atan(rel_diff[0]/game_window_rect__center[0])) * ((fov[0]/2)/45)
y_degs = degrees(atan(rel_diff[1] / game_window_rect__center[0])) * ((fov[1]/2)/45)
rel_diff[0] = pixels_per_degree * x_degs
rel_diff[1] = pixels_per_degree * y_degs
return rel_diff, (x_degs+y_degs)
get_move_angle__new((900, 540), (0, 0, 1920, 1080), 16364/360, (106.26, 73.74))
# Output will be: ([-191.93420990140876, 0.0], -4.222458785413539)
# But it's not accurate, overall x_degs must be more or less than -4.22...
有没有办法将2D画面的X, Y
坐标精确转换成3D游戏的十字准线移动度数?
一定有办法,我只是想不通...
屏幕中心和边缘之间的中点不等于视野除以四。 正如您所注意到的,这种关系是非线性的。
屏幕上的分数位置 (0-1) 与屏幕中间之间的角度可以计算如下。 这是针对水平旋转(即围绕垂直轴),所以我们只考虑屏幕上的 X 位置。
# angle is the angle in radians that the camera needs to
# rotate to aim at the point
# px is the point x position on the screen, normalised by
# the resolution (so 0.0 for the left-most pixel, 0.5 for
# the centre and 1.0 for the right-most
# FOV is the field of view in the x dimension in radians
angle = math.atan((x-0.5)*2*math.tan(FOV/2))
对于 100 度的视野和零 x,这给了我们 -50 度的旋转(正好是视野的一半)。 对于 0.25 的 x(边缘和中间之间的中间位置),我们得到大约 -31 度的旋转。
请注意,对于任何给定的视野, 2*math.tan(FOV/2)
部分都是常数,因此您可以提前计算并存储它。 然后它就变成了(假设我们将其命名为z
):
angle = math.atan((x-0.5)*z)
只需对 x 和 y 执行此操作,它就可以工作。
编辑/更新:
这是一个完整的功能。 我已经测试过了,它似乎有效。
import math
def get_angles(aim_target, window_size, fov):
"""
Get (x, y) angles from center of image to aim_target.
Args:
aim_target: pair of numbers (x, y) where to aim
window_size: size of area (x, y)
fov: field of view in degrees, (horizontal, vertical)
Returns:
Pair of floating point angles (x, y) in degrees
"""
fov = (math.radians(fov[0]), math.radians(fov[1]))
x_pos = aim_target[0]/(window_size[0]-1)
y_pos = aim_target[1]/(window_size[1]-1)
x_angle = math.atan((x_pos-0.5)*2*math.tan(fov[0]/2))
y_angle = math.atan((y_pos-0.5)*2*math.tan(fov[1]/2))
return (math.degrees(x_angle), math.degrees(y_angle))
print(get_angles(
(0, 0), (1920, 1080), (100, 67.67)
), "should be around -50, -33.835")
print(get_angles(
(1919, 1079), (1920, 1080), (100, 67.67)
), "should be around 50, 33.835")
print(get_angles(
(959.5, 539.5), (1920, 1080), (100, 67.67)
), "should be around 0, 0")
print(get_angles(
(479.75, 269.75), (1920, 1080), (100, 67.67)
), "should be around 30.79, 18.53")
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.