繁体   English   中英

如何将屏幕 x,y(笛卡尔坐标)转换为 3D 世界空间十字准线移动角度(screenToWorld)?

[英]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 anglesvertical 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM