Hololens2上,屏幕某个位置的像素点显示在三维空间

项目的github地址:AliceShum/HololensScreenPos2WorldPos (github.com)

Hololens拍照之后,有时需要获取照片某个像素点在真实世界的坐标,比如要获取照片上的人所在的像素点对应真实世界的位置,下面是我的方法。

Hololens拍照部分,参考官方文档

把拍照时的PhotoCaptureFrame保存下来,拍照方法提供了获取Projection Matrix、Camera to World Matrix两个变换矩阵,可以通过它们将照片上的像素点转成对应unity空间的三维坐标点。

相机的两个矩阵参考:(40条消息) 相机标定(二)——图像坐标与世界坐标转换_相机坐标系与世界坐标系转换公式_white_Learner的博客-CSDN博客

public HoloLensCameraStream.Resolution resolution = new HoloLensCameraStream.Resolution(3904, 2196);
public PhotoCaptureFrame savedPhotoCaptureFrame;

//根据照片的大小设置Quad的大小;这个quad是把拍照时的图片放在材质上,再按照下面的设置,可以看到当时拍摄时相机的画面,放在空间中,恰好和拍照时镜头看到的画面一致
//根据拍照时像素中心点的位置设置Quad的位置
public void SetQuadSizeAndPos()
    {
        //todo 把相机拍的照片设置为quad的材质纹理

        Matrix4x4 cameraToWorldMatrix;
        bool isSuccess1 = savedPhotoCaptureFrame.TryGetCameraToWorldMatrix(out cameraToWorldMatrix);
        Matrix4x4 projectionMatrix;
        bool isSuccess2 = savedPhotoCaptureFrame.TryGetProjectionMatrix(out projectionMatrix);
        if (!isSuccess1)
        {
            return;
        }
        if (!isSuccess2)
        {
            return;
        }

        Vector3 imageCenterDirection = LocatableCameraUtils.PixelCoordToWorldCoord(cameraToWorldMatrix, projectionMatrix, resolution, new Vector2(resolution.width / 2, resolution.height / 2));
        Vector3 imageTopLeftDirection = LocatableCameraUtils.PixelCoordToWorldCoord(cameraToWorldMatrix, projectionMatrix, resolution, new Vector2(0, 0));
        Vector3 imageTopRightDirection = LocatableCameraUtils.PixelCoordToWorldCoord(cameraToWorldMatrix, projectionMatrix, resolution, new Vector2(resolution.width, 0));
        Vector3 imageBotLeftDirection = LocatableCameraUtils.PixelCoordToWorldCoord(cameraToWorldMatrix, projectionMatrix, resolution, new Vector2(0, resolution.height));
        Vector3 imageBotRightDirection = LocatableCameraUtils.PixelCoordToWorldCoord(cameraToWorldMatrix, projectionMatrix, resolution, new Vector2(resolution.width, resolution.height));

        //quad物体的size按照截图的大小来设置
        float x = Vector3.Distance(imageTopLeftDirection, imageTopRightDirection);
        float y = Vector3.Distance(imageTopLeftDirection, imageBotLeftDirection);
        quad.transform.localScale = new Vector3(x, y, 1);

        Vector3 holoCamPos = GetHololensCameraPosByMatrix(cameraToWorldMatrix);
        quad.gameObject.transform.position = imageCenterDirection + holoCamPos;

        quad.transform.localEulerAngles = GetFaceHololensCameraEulerAngles(cameraToWorldMatrix);
    }

    
    //获取拍照时Hololens镜头的位置
    Vector3 GetHololensCameraPosByMatrix(Matrix4x4 cameraToWorldMatrix)
    {
        Vector3 pos = new Vector3(cameraToWorldMatrix[12], cameraToWorldMatrix[13], cameraToWorldMatrix[14]);
        return pos;
    }

    //获取拍照时Hololens镜头的旋转角度
    Vector3 GetHololensCameraAngleByMatrix(Matrix4x4 cameraToWorldMatrix)
    {
        Vector3 angle = cameraToWorldMatrix.rotation.eulerAngles;
        return angle;
    }

    //获取物体面向Hololens镜头的朝向角度
    Vector3 GetFaceHololensCameraEulerAngles(Matrix4x4 cameraToWorldMatrix)
    {
        Vector3 inverseNormal = -cameraToWorldMatrix.GetColumn(2);
        Quaternion quadRotation = Quaternion.LookRotation(inverseNormal, cameraToWorldMatrix.GetColumn(1));
        return quadRotation.eulerAngles;
    }
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class LocatableCameraUtils
{
    /// <summary>
    /// Helper method for pixel projection into Unity3D world space.
    /// This method return a Vector3 with direction: optical center of the camera to the pixel coordinate
    /// The method is based on: https://developer.microsoft.com/en-us/windows/mixed-reality/locatable_camera#pixel_to_application-specified_coordinate_system
    /// </summary>
    /// <param name="cameraToWorldMatrix">The camera to Unity world matrix.</param>
    /// <param name="projectionMatrix">Projection Matrix.</param>
    /// <param name="pixelCoordinates">The coordinate of the pixel that should be converted to world-space.</param>
    /// <param name="cameraResolution">The resolution of the image that the pixel came from.</param>
    /// <returns>Vector3 with direction: optical center to camera world-space coordinates</returns>
    public static Vector3 PixelCoordToWorldCoord(Matrix4x4 cameraToWorldMatrix, Matrix4x4 projectionMatrix, HoloLensCameraStream.Resolution cameraResolution, Vector2 pixelCoordinates)
    {
        pixelCoordinates = ConvertPixelCoordsToScaledCoords(pixelCoordinates, cameraResolution); // -1 to 1 coords

        float focalLengthX = projectionMatrix.GetColumn(0).x;
        float focalLengthY = projectionMatrix.GetColumn(1).y;
        float centerX = projectionMatrix.GetColumn(2).x;
        float centerY = projectionMatrix.GetColumn(2).y;

        // On Microsoft Webpage the centers are normalized 
        float normFactor = projectionMatrix.GetColumn(2).z;
        centerX = centerX / normFactor;
        centerY = centerY / normFactor;

        Vector3 dirRay = new Vector3((pixelCoordinates.x - centerX) / focalLengthX, (pixelCoordinates.y - centerY) / focalLengthY, 1.0f / normFactor); //Direction is in camera space
        Vector3 direction = new Vector3(Vector3.Dot(cameraToWorldMatrix.GetRow(0), dirRay), Vector3.Dot(cameraToWorldMatrix.GetRow(1), dirRay), Vector3.Dot(cameraToWorldMatrix.GetRow(2), dirRay));

        return direction;
    }

    public static Vector3 GetNormalOfPose(Matrix4x4 pose)
    {
        return new Vector3(Vector3.Dot(Vector3.forward, pose.GetRow(0)), Vector3.Dot(Vector3.forward, pose.GetRow(1)), Vector3.Dot(Vector3.forward, pose.GetRow(2)));
    }

    public static Quaternion GetRotationFacingView(Matrix4x4 viewTransform)
    {
        return Quaternion.LookRotation(-viewTransform.GetColumn(2), viewTransform.GetColumn(1));
    }

    public static Matrix4x4 BytesToMatrix(byte[] inMatrix)
    {
        //Then convert the floats to a matrix.
        Matrix4x4 outMatrix = new Matrix4x4
        {
            m00 = inMatrix[0],
            m01 = inMatrix[1],
            m02 = inMatrix[2],
            m03 = inMatrix[3],
            m10 = inMatrix[4],
            m11 = inMatrix[5],
            m12 = inMatrix[6],
            m13 = inMatrix[7],
            m20 = inMatrix[8],
            m21 = inMatrix[9],
            m22 = inMatrix[10],
            m23 = inMatrix[11],
            m30 = inMatrix[12],
            m31 = inMatrix[13],
            m32 = inMatrix[14],
            m33 = inMatrix[15]
        };
        return outMatrix;
    }

    /// <summary>
    /// Helper method for converting into UnityEngine.Matrix4x4
    /// </summary>
    /// <param name="matrixAsArray"></param>
    /// <returns></returns>
    public static Matrix4x4 ConvertFloatArrayToMatrix4x4(float[] matrixAsArray)
    {
        //There is probably a better way to be doing this but System.Numerics.Matrix4x4 is not available 
        //in Unity and we do not include UnityEngine in the plugin.
        Matrix4x4 m = new Matrix4x4();
        m.m00 = matrixAsArray[0];
        m.m01 = matrixAsArray[1];
        m.m02 = matrixAsArray[2];
        m.m03 = matrixAsArray[3];
        m.m10 = matrixAsArray[4];
        m.m11 = matrixAsArray[5];
        m.m12 = matrixAsArray[6];
        m.m13 = matrixAsArray[7];
        m.m20 = matrixAsArray[8];
        m.m21 = matrixAsArray[9];
        m.m22 = matrixAsArray[10];
        m.m23 = matrixAsArray[11];
        m.m30 = matrixAsArray[12];
        m.m31 = matrixAsArray[13];
        m.m32 = matrixAsArray[14];
        m.m33 = matrixAsArray[15];

        return m;
    }

    /// <summary>
    /// Converts pixel coordinates to screen-space coordinates that span from -1 to 1 on both axes.
    /// This is the format that is required to determine the z-depth of a given pixel taken by the HoloLens camera.
    /// </summary>
    /// <param name="pixelCoords">The coordinate of the pixel that should be converted to screen-space.</param>
    /// <param name="res">The resolution of the image that the pixel came from.</param>
    /// <returns>A 2D vector with values between -1 and 1, representing the left-to-right scale within the image dimensions.</returns>
    static Vector2 ConvertPixelCoordsToScaledCoords(Vector2 pixelCoords, HoloLensCameraStream.Resolution resolution)
    {
        float halfWidth = (float)resolution.width / 2f;
        float halfHeight = (float)resolution.height / 2f;

        //Translate registration to image center;
        pixelCoords.x -= halfWidth;
        pixelCoords.y -= halfHeight;

        //Scale pixel coords to percentage coords (-1 to 1)
        pixelCoords = new Vector2(pixelCoords.x / halfWidth, pixelCoords.y / halfHeight * -1f);

        return pixelCoords;
    }
}

namespace HoloLensCameraStream
{
    /// <summary>
    /// A structure for holding a resolution.
    /// </summary>
    public struct Resolution
    {
        /// <summary>
        /// The width property.
        /// </summary>
        public readonly int width;

        /// <summary>
        /// The height property.
        /// </summary>
        public readonly int height;

        public Resolution(int width, int height)
        {
            this.width = width;
            this.height = height;
        }
    }
}

参考了github上的案例。因为它的项目有报错,我只能这样写。

注意:上面的PixelCoordToWorldCoord方法计算的Z是固定值(约等于1),因此位置不对的。最后我还是以另一种方式获得正确的深度值:打开Spatial Awareness,通过射线检测到环境网格的碰撞点。

RaycastHit hit;
int layerAsLayerMask = (1 << 31); //31 --> 环境网格 Spatial Awareness
Vector3 ori = GetHololensCameraPosByMatrix(cameraToWorldMatrix); //拍照时Hololens镜头的位置
Vector3 dir = pos; //pos是你需要计算的某个像素点,在使用PixelCoordToWorldCoord方法后,得到的坐标
if (Physics.Raycast(ori , dir , out hit, Mathf.Infinity, layerAsLayerMask))
            {
                Debug.Log("最终结果:" + hit.point);
                
            }

最后的效果:黑底的是拍到的照片,白底的是要拍照的物体。相机位置是在拍照时Hololens镜头的位置,白底上的红点是要获取的最终空间位置,黑底上的蓝色点是照片上等比分开的像素点。可以看到,蓝色点和绿色点大致对应。白色线条是模拟从镜头出发朝向某个二维像素点发射的射线,在两个人身上的部位一样。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值