视觉高级篇21#如何添加相机,用透视原理对物体进行投影?(代码片段)

凯小默 凯小默     2023-01-11     264

关键词:

说明

【跟月影学可视化】学习笔记。

如何理解相机和视图矩阵?

用一个三维坐标(Position)和一个三维向量方向(LookAt Target)来表示 WebGL 的三维世界的一个相机。要绘制以相机为观察者的图形,需要用一个变换,将世界坐标转换为相机坐标。这个变换的矩阵就是视图矩阵(ViewMatrix)

怎么计算视图矩阵?

  1. 先计算相机的模型矩阵
  2. 然后对矩阵使用 lookAt 函数,得到的矩阵就是视图矩阵的逆矩阵。
  3. 最后再对这个逆矩阵求一次逆,就可以得到视图矩阵。

用代码的方式表示:

function updateCamera(eye, target = [0, 0, 0]) 
	const [x, y, z] = eye;
	// 设置相机初始位置矩阵 m
	const m = new Mat4(
		1, 0,0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		x, y, z, 1,
	);
	const up = [0, 1, 0];
	m.lookAt(eye, target, up).inverse();
	renderer.uniforms.viewMatrix = m;

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>在绘制圆柱体里加入相机</title>
        <style>
            canvas 
                border: 1px dashed rgb(250, 128, 114);
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script type="module">
            import  Mat4  from './common/lib/math/Mat4.js';
            import  multiply  from './common/lib/math/functions/Mat4Func.js';
            import  cross, subtract, normalize  from './common/lib/math/functions/Vec3Func.js';
            import  normalFromMat4  from './common/lib/math/functions/Mat3Func.js';

            const vertex = `
                attribute vec3 a_vertexPosition;
                attribute vec4 color;
                attribute vec3 normal;

                varying vec4 vColor;
                varying float vCos;
                uniform mat4 projectionMatrix;
                uniform mat4 modelMatrix;
                uniform mat4 viewMatrix;
                uniform mat3 normalMatrix;
                
                const vec3 lightPosition = vec3(1, 0, 0);

                void main() 
                    gl_PointSize = 1.0;
                    vColor = color;
                    vec4 pos =  viewMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
                    vec4 lp = viewMatrix * vec4(lightPosition, 1.0);
                    vec3 invLight = lightPosition - pos.xyz;
                    vec3 norm = normalize(normalMatrix * normal);
                    vCos = max(dot(normalize(invLight), norm), 0.0);
                    gl_Position = projectionMatrix * pos;
                
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform vec4 lightColor;
                varying vec4 vColor;
                varying float vCos;

                void main() 
                    gl_FragColor.rgb = vColor.rgb + vCos * lightColor.a * lightColor.rgb;
                    gl_FragColor.a = vColor.a;
                
            `;

            const canvas = document.querySelector("canvas");
            // 开启深度检测
            const renderer = new GlRenderer(canvas, 
                depth: true
            );
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1]) 
                const positions = [];
                const cells = [];
                const color = [];
                const cap = [[0, 0]];
                const h = 0.5 * height;
                const normal = [];

                // 顶和底的圆
                for(let i = 0; i <= segments; i++) 
                    const theta = Math.PI * 2 * i / segments;
                    const p = [radius * Math.cos(theta), radius * Math.sin(theta)];
                    cap.push(p);
                

                positions.push(...cap.map(([x, y]) => [x, y, -h]));
                normal.push(...cap.map(() => [0, 0, -1]));

                for(let i = 1; i < cap.length - 1; i++) 
                    cells.push([0, i, i + 1]);
                
                cells.push([0, cap.length - 1, 1]);

                let offset = positions.length;
                positions.push(...cap.map(([x, y]) => [x, y, h]));
                normal.push(...cap.map(() => [0, 0, 1]));

                for(let i = 1; i < cap.length - 1; i++) 
                    cells.push([offset, offset + i, offset + i + 1]);
                
                cells.push([offset, offset + cap.length - 1, offset + 1]);

                color.push(...positions.map(() => colorCap));

                const tmp1 = [];
                const tmp2 = [];
                // 侧面,这里需要求出侧面的法向量
                offset = positions.length;
                for(let i = 1; i < cap.length; i++) 
                    const a = [...cap[i], h];
                    const b = [...cap[i], -h];
                    const nextIdx = i < cap.length - 1 ? i + 1 : 1;
                    const c = [...cap[nextIdx], -h];
                    const d = [...cap[nextIdx], h];

                    positions.push(a, b, c, d);

                    const norm = [];
                    cross(norm, subtract(tmp1, b, a), subtract(tmp2, c, a));
                    normalize(norm, norm);
                    normal.push(norm, norm, norm, norm); // abcd四个点共面,它们的法向量相同

                    color.push(colorSide, colorSide, colorSide, colorSide);
                    cells.push([offset, offset + 1, offset + 2], [offset, offset + 2, offset + 3]);
                    offset += 4;
                

                return  positions, cells, color, normal ;
            

            const geometry = cylinder(0.2, 1.0, 400,
                [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
            );

            // 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
            renderer.uniforms.projectionMatrix = [
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, -1, 0,
                0, 0, 0, 1,
            ];

            renderer.uniforms.lightColor = [218/255, 165/255, 32/255, 0.6];// goldenrod rgb(218, 165, 32)

            function updateCamera(eye, target = [0, 0, 0]) 
                const [x, y, z] = eye;
                // 设置相机初始位置矩阵 m
                const m = new Mat4(
                    1, 0,0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    x, y, z, 1,
                );
                const up = [0, 1, 0];
                m.lookAt(eye, target, up).inverse();
                renderer.uniforms.viewMatrix = m;
            

            // 设置相机位置
            updateCamera([0.5, 0, 0.5]);

            renderer.setMeshData([
                
                    positions: geometry.positions,
                    attributes: 
                        color: geometry.color,
                        normal: geometry.normal
                    ,
                    cells: geometry.cells,
                ,
            ]);

            renderer.uniforms.modelMatrix = new Mat4(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1,
            );

            function update() 
                const modelViewMatrix = multiply([], renderer.uniforms.viewMatrix, renderer.uniforms.modelMatrix);
                
                renderer.uniforms.modelViewMatrix = modelViewMatrix;
                renderer.uniforms.normalMatrix = normalFromMat4([], modelViewMatrix);
                requestAnimationFrame(update);
            
            update();

            renderer.render();
        </script>
    </body>
</html>

剪裁空间和投影对 3D 图像的影响

WebGL 的默认坐标范围是从 -1 到 1 的。只有当图像的 x、y、z 的值在 -1 到 1 区间内才会被显示在画布上,而在其他位置上的图像都会被剪裁掉。

给下面图形分别给 x、y、z 轴增加 0.5 的平移

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>圆柱体被剪裁效果</title>
        <style>
            canvas 
                border: 1px dashed rgb(250, 128, 114);
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script type="module">
            import  multiply  from './common/lib/math/functions/Mat4Func.js';
            import  cross, subtract, normalize  from './common/lib/math/functions/Vec3Func.js';
            import  normalFromMat4  from './common/lib/math/functions/Mat3Func.js';

            const vertex = `
                attribute vec3 a_vertexPosition;
                attribute vec4 color;
                attribute vec3 normal;

                varying vec4 vColor;
                varying float vCos;
                uniform mat4 projectionMatrix;
                uniform mat4 modelMatrix;
                uniform mat3 normalMatrix;
                
                const vec3 lightPosition = vec3(1, 0, 0);

                void main() 
                    gl_PointSize = 1.0;
                    vColor = color;
                    vec4 pos =  modelMatrix * vec4(a_vertexPosition, 1.0);
                    vec4 lp = vec4(lightPosition, 1.0);
                    vec3 invLight = lightPosition - pos.xyz;
                    vec3 norm = normalize(normalMatrix * normal);
                    vCos = max(dot(normalize(invLight), norm), 0.0);
                    gl_Position = projectionMatrix * pos;
                
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform vec4 lightColor;
                varying vec4 vColor;
                varying float vCos;

                void main() 
                    gl_FragColor.rgb = vColor.rgb + vCos * lightColor.a * lightColor.rgb;
                    gl_FragColor.a = vColor.a;
                
            `;

            const canvas = document.querySelector("canvas");
            // 开启深度检测
            const renderer = new GlRenderer(canvas, 
                depth: true
            );
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1]) 
                const positions = [];
                const cells = [];
                const color = [];
                const cap = [[0, 0]];
                const h = 0.5 * height;
                const normal = [];

                // 顶和底的圆
                for(let i = 0; i <= segments; i++) 
                    const theta = Math.PI * 2 * i / segments;
                    const p = 

视觉高级篇23#如何模拟光照让3d场景更逼真?(上)(代码片段)

说明【跟月影学可视化】学习笔记。光照效果简介物体的光照效果是由光源、介质(物体的材质)和反射类型决定的,而反射类型又由物体的材质特点决定。在3D光照模型中,根据不同的光源特点分为四种:环... 查看详情

对静态相机的背景图像进行建模以检测移动物体

...】:2013-10-2214:28:46【问题描述】:我目前正在准备计算机视觉考试,并且想学习一些东西。问题是对静态摄像机的背景进行建模,以检测汽车和行人等移动物体。我最初认为最好的答案是使用帧差技术,但它有一些缺陷。回答这... 查看详情

深度相机哪家强?

随着机器视觉、自动驾驶、机器人的火爆,采用深度相机采集环境的深度信息然后进行物体识别、环境建模等越来普遍;相对于传统2D相机,3D相机增加了一维的深息,因而,能够更好的对真实世界进行描述;在许多领域如安防... 查看详情

相机矩阵(cameramatrix)

...影面上所得到的图形。透视图与人们观看物体时所产生的视觉效果非常接近,所以它能更加生动形象地表现建筑外貌及内部装饰。在已有实景实物的情况下,通过拍照或摄像即能得到透视图;对于尚在设计、规划中的建筑物则作... 查看详情

视觉高级篇25#如何用法线贴图模拟真实物体表面(代码片段)

...同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程 查看详情

视觉高级篇24#如何模拟光照让3d场景更逼真?(下)(代码片段)

说明【跟月影学可视化】学习笔记。什么是镜面反射?如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。越光滑的材质,它的镜面反射效果也就越强,并且物体表面... 查看详情

视觉高级篇24#如何模拟光照让3d场景更逼真?(下)(代码片段)

说明【跟月影学可视化】学习笔记。什么是镜面反射?如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。越光滑的材质,它的镜面反射效果也就越强,并且物体表面... 查看详情

机器视觉检测都检测啥?原理是啥?

视觉检测是一种利用人眼视觉系统进行检测的技术,具体就是把被检测物体的图像投射到摄像头或人眼中,通过图像处理算法对图像进行分析,从而判断被检测物体是否符合要求。通俗点说,就像我们看东西一样,把被检测物体... 查看详情

新型无镜头相机助力计算机视觉

计算机视觉处理高分辨率的图像需要非常多的计算量,因此很多数据集的图像分辨率都非常小。而近日,工程师们用一块玻璃、一个光电探测器和一些软件,开发了一种「透视」相机,这种不带镜头的相机能拍摄分辨率非常小的... 查看详情

计算机视觉立体视觉极简一览(代码片段)

一、立体视觉概述        客观世界在空间上是3-D的,所以对视觉的研究和应用从根本上说应该是3-D的。现有的大多数图像采集装置所获取的图像本身是在2-D平面上的,尽管其中可以含有3-D物体的空间信息。要从图像... 查看详情

视觉高级篇22#如何用仿射变换来移动和旋转3d物体?(代码片段)

说明【跟月影学可视化】学习笔记。三维仿射变换:平移对于平移变换来说,如果向量P(x0​x_0​x0​​,y0y_0y0​​,z0​z_0​z0​​)沿着向量Q(x1x_1x1​​,y1​y_1​y1​​,z1​z_1​z1​​)平移,只需要让P加上Q,就能得... 查看详情

视觉高级篇22#如何用仿射变换来移动和旋转3d物体?(代码片段)

说明【跟月影学可视化】学习笔记。三维仿射变换:平移对于平移变换来说,如果向量P(x0​x_0​x0​​,y0y_0y0​​,z0​z_0​z0​​)沿着向量Q(x1x_1x1​​,y1​y_1​y1​​,z1​z_1​z1​​)平移,只需要让P加上Q,就能得... 查看详情

如何利用opencv计算图像畸变系数,并进行校正与摄像机标定?

如果知道图像,不知道相机还怎么通过相机来标定畸变。1:只给定一张图片可以根据图像中相关特征进行标定,简单讲就是利用:lineisstraight这个原理。2:目前最常用的方法,是通过二维标定板,通过对reprojectionerror最小化进行... 查看详情

双目立体视觉

1.简介:双目视觉是模拟人类视觉原理,使用计算机被动感知距离的方法。从两个或者多个点观察一个物体,获取在不同视角下的图像,根据图像之间像素的匹配关系,通过三角测量原理计算出像素之间的偏移来获取物体的三维... 查看详情

机器视觉识别技术有些啥种类

机器视觉识别技术包括以下几种常见的类型:形状匹配:通过比较目标物体的形状和已知形状的样本,识别出目标物体的种类。颜色识别:通过对目标物体的颜色进行分析和比较,识别出目标物体的种类或状态。纹理分析:通过... 查看详情

利用opencv的函数warpperspective()作图像的透视变换(代码片段)

...像投影到新的成像平面上。图像的透视变换通常用来解决相机的视线与物体所在平面不垂直的问题。比如,下面这幅图中,如果相机的视线与正方形是垂直的,那么应该是下面这样的成像效果:但我们相机的视线... 查看详情

第一篇基础原理篇

...需要编程语言,对绝大部分人来说,使用的编程语言称为高级程序设计语言,如,c,c++,java等。但是计算机不认识高级语言编写的程序,编好的程序需要进行编译变成计算机能够识别的机器语言程序,而这需要编译器和汇编器的... 查看详情

61相机投影原理

...建平:《相机成像原理》PPT,《学习opencv》以及《计算机视觉中的多视图几何》,如有错误欢迎探讨。1相机投影中的坐标系在《相 查看详情