如何在CSS3 3D变换中匹配真实照片和物体的3D透视

问题描述 投票:0回答:4

情况:

在我的特定情况下,我有 3D 相框图像和 2D 照片。我希望 2D 照片能够使用 CSS3 变换功能(旋转、缩放、倾斜)来匹配 3D 框架。

问题:

我无法使用手动方法(即输入旋转值并观察其作用)来精确匹配两者。

理想的解决方案#1

在线视觉工具可以让您拖动照片的角(就像Photoshop一样),并且它可以为您提供正确的CSS3变换值。

理想的解决方案#2

存在非视觉工具 - 与以前相同,但您手动输入 4 点坐标(图像角),它会为您提供正确的 CSS3 变换值。

这个问题的真正解答

如果没有这样的工具(我的搜索没有找到),我希望有人尝试解释其背后的数学原理,以便我可以自己计算 - 如果它甚至可能

我准备了 JSFiddle 演示供您摆弄: 演示

/* Main issue here */

.transform {
  transform: rotateX(34deg) rotateZ(13deg) rotateY(-10deg) scaleY(1) scaleX(1) skewY(0deg) skewX(0deg) translateY(0px) translateX(20px);
  transform-origin: 50% 0% 0;
}
/* Supporting styles */

.container {
  position: relative;
  width: 500px;
  height: 500px;
}
.frame,
.photo {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.photo {
  top: 50px;
  left: 95px;
  right: 65px;
  bottom: 270px;
}
.frame img,
.photo img {
  width: 100%
}
.frame {
  z-index: 2;
}
<div class="container">

  <div class="frame">
    <img src="http://cdn.idesigned.cz/img/cc08acc7b9b08ab53bf935d720210f13.png" />
  </div>

  <div class="photo">
    <div class="transform">
      <img src="https://static.pexels.com/photos/7976/pexels-photo.jpg" />
    </div>
  </div>

</div>

css transform css-transforms
4个回答
20
投票

如果您可以使用 3 维变换(例如

rotateZ
),那么您还可以提供
matrix3d
,您可以根据所需的点对应关系进行计算。

这是一个小提琴:https://jsfiddle.net/szym/03s5mwjv/

我正在使用 numeric.js 求解一组 4 个线性方程,以找到将

src
转换为
dst
的透视变换矩阵。这本质上与 OpenCV 中的
getPerspectiveTransform
中的数学相同。

计算出的二维透视变换是使用

齐次坐标的 3x3 矩阵。 CSS matrix3d

 是一个使用齐次坐标的 4x4 矩阵,因此我们需要为 
z
 轴添加标识行/列。此外,
matrix3d
 按列优先顺序指定。

一旦获得

matrix3d

,您就可以将其粘贴到样式表中。但请记住,矩阵是假设 
(0, 0)
 作为原点计算的,因此您还需要设置 
transformOrigin: 0 0

// Computes the matrix3d that maps src points to dst. function computeTransform(src, dst) { // src and dst should have length 4 each var count = 4; var a = []; // (2*count) x 8 matrix var b = []; // (2*count) vector for (var i = 0; i < 2 * count; ++i) { a.push([0, 0, 0, 0, 0, 0, 0, 0]); b.push(0); } for (var i = 0; i < count; ++i) { var j = i + count; a[i][0] = a[j][3] = src[i][0]; a[i][1] = a[j][4] = src[i][1]; a[i][2] = a[j][5] = 1; a[i][3] = a[i][4] = a[i][5] = a[j][0] = a[j][1] = a[j][2] = 0; a[i][6] = -src[i][0] * dst[i][0]; a[i][7] = -src[i][1] * dst[i][0]; a[j][6] = -src[i][0] * dst[i][1]; a[j][7] = -src[i][1] * dst[i][1]; b[i] = dst[i][0]; b[j] = dst[i][1]; } var x = numeric.solve(a, b); // matrix3d is homogeneous coords in column major! // the z coordinate is unused var m = [ x[0], x[3], 0, x[6], x[1], x[4], 0, x[7], 0, 0, 1, 0, x[2], x[5], 0, 1 ]; var transform = "matrix3d("; for (var i = 0; i < m.length - 1; ++i) { transform += m[i] + ", "; } transform += m[15] + ")"; return transform; } // Collect the four corners by user clicking in the corners var dst = []; document.getElementById('frame').addEventListener('mousedown', function(evt) { // Make sure the coordinates are within the target element. var box = evt.target.getBoundingClientRect(); var point = [evt.clientX - box.left, evt.clientY - box.top]; dst.push(point); if (dst.length == 4) { // Once we have all corners, compute the transform. var img = document.getElementById('img'); var w = img.width, h = img.height; var transform = computeTransform( [ [0, 0], [w, 0], [w, h], [0, h] ], dst ); document.getElementById('photo').style.visibility = 'visible'; document.getElementById('transform').style.transformOrigin = '0 0'; document.getElementById('transform').style.transform = transform; document.getElementById('result').innerHTML = transform; } });
.container {
  position: relative;
  width: 50%;
}
  
#frame,
#photo {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
  
#photo {
  visibility: hidden;
}
  
#frame img,
#photo img {
  width: 100%
}
  
#photo {
  opacity: 0.7;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js"></script>
  <p id="result">Click the desired top-left, top-right, bottom-right, bottom-left corners
  <div class="container">
    <div id="frame">
      <img src="http://cdn.idesigned.cz/img/cc08acc7b9b08ab53bf935d720210f13.png" />
    </div>

    <div id="photo">
      <div id="transform">
        <img id="img" src="http://placehold.it/350x150" />
      </div>
    </div>

  </div>


6
投票
我已经在 Math SE 上写了一个答案

,关于如何计算变换矩阵,以使用投影变换将一个图像的角点映射到四个给定的坐标。它包含计算中实际发生的情况的详细信息。它还有一些 CSS,我将 原始演示 改编为您的示例场景:

function adj(m) { // Compute the adjugate of m return [ m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4], m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5], m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3] ]; } function multmm(a, b) { // multiply two matrices var c = Array(9); for (var i = 0; i != 3; ++i) { for (var j = 0; j != 3; ++j) { var cij = 0; for (var k = 0; k != 3; ++k) { cij += a[3*i + k]*b[3*k + j]; } c[3*i + j] = cij; } } return c; } function multmv(m, v) { // multiply matrix and vector return [ m[0]*v[0] + m[1]*v[1] + m[2]*v[2], m[3]*v[0] + m[4]*v[1] + m[5]*v[2], m[6]*v[0] + m[7]*v[1] + m[8]*v[2] ]; } function basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) { // map basis to these points var m = [ x1, x2, x3, y1, y2, y3, 1, 1, 1 ]; var v = multmv(adj(m), [x4, y4, 1]); return multmm(m, [ v[0], 0, 0, 0, v[1], 0, 0, 0, v[2] ]); } function general2DProjection( x1s, y1s, x1d, y1d, x2s, y2s, x2d, y2d, x3s, y3s, x3d, y3d, x4s, y4s, x4d, y4d ) { console.log(Array.prototype.join.call(arguments, ", ")); var s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s); var d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d); return multmm(d, adj(s)); } function transform2d(elt, x1, y1, x2, y2, x3, y3, x4, y4) { var w = elt.offsetWidth, h = elt.offsetHeight; var t = general2DProjection (0, 0, x1, y1, w, 0, x2, y2, 0, h, x3, y3, w, h, x4, y4); for(i = 0; i != 9; ++i) t[i] = t[i]/t[8]; t = [t[0], t[3], 0, t[6], t[1], t[4], 0, t[7], 0 , 0 , 1, 0 , t[2], t[5], 0, t[8]]; t = "matrix3d(" + t.join(", ") + ")"; elt.style["-webkit-transform"] = t; elt.style["-moz-transform"] = t; elt.style["-o-transform"] = t; elt.style.transform = t; } corners = [100, 50, 300, 50, 100, 150, 300, 150]; function update() { var box = document.getElementById("photo"); transform2d(box, corners[0], corners[1], corners[2], corners[3], corners[4], corners[5], corners[6], corners[7]); for (var i = 0; i != 8; i += 2) { var elt = document.getElementById("marker" + i); elt.style.left = corners[i] + "px"; elt.style.top = corners[i + 1] + "px"; } document.getElementById("matrix").textContent = box.style.transform; } function move(evnt) { if (currentcorner < 0) return; corners[currentcorner] = evnt.pageX; corners[currentcorner + 1] = evnt.pageY; console.log(corners); update(); } currentcorner = -1; window.addEventListener('load', function() { document.documentElement.style.margin="0px"; document.documentElement.style.padding="0px"; document.body.style.margin="0px"; document.body.style.padding="0px"; update(); }); window.addEventListener('mousedown', function(evnt) { var x = evnt.pageX, y = evnt.pageY, dx, dy; var best = 400; // 20px grab radius currentcorner = -1; for (var i = 0; i != 8; i += 2) { dx = x - corners[i]; dy = y - corners[i + 1]; if (best > dx*dx + dy*dy) { best = dx*dx + dy*dy; currentcorner = i; } } move(evnt); evnt.preventDefault(); }, true); window.addEventListener('mouseup', function(evnt) { currentcorner = -1; }, true) window.addEventListener('mousemove', move, true);
/* Supporting styles */

#photo {
  position: absolute;
  z-index: 1;
  transform-origin: 0% 0% 0;
}
.dot {
  position: absolute;
  z-index: 2;
  margin: -0.5ex;
  padding: 0ex;
  width: 1ex;
  height: 1ex;
  border-radius: 0.5ex;
  background-color: #ff0000;
}
<img id="photo" src="https://static.pexels.com/photos/7976/pexels-photo.jpg" />
<img class="frame" src="http://cdn.idesigned.cz/img/cc08acc7b9b08ab53bf935d720210f13.png" />
<div class="dot" id="marker0"></div>
<div class="dot" id="marker2"></div>
<div class="dot" id="marker4"></div>
<div class="dot" id="marker6"></div>
<div id="matrix"></div>

该公式仅通过三个算术运算即可实现:

+

-
*
。你甚至不需要
/
(如果你使用辅助而不是逆矩阵),更不用说区分大小写、平方根或任何其他类似的东西了。

如果您更喜欢 Stack Overflow(并建立交叉引用),请参阅

从 3d 视角重新绘制图像到 2d


3
投票

// Computes the matrix3d that maps src points to dst. function compute_transform(src, dst) { // src and dst should have length 4 each var count = 4; var a = []; // (2*count) x 8 matrix var b = []; // (2*count) vector for (var i = 0; i < 2 * count; ++i) { a.push([0, 0, 0, 0, 0, 0, 0, 0]); b.push(0); } for (var i = 0; i < count; ++i) { var j = i + count; a[i][0] = a[j][3] = src[i][0]; a[i][1] = a[j][4] = src[i][1]; a[i][2] = a[j][5] = 1; a[i][3] = a[i][4] = a[i][5] = a[j][0] = a[j][1] = a[j][2] = 0; a[i][6] = -src[i][0] * dst[i][0]; a[i][7] = -src[i][1] * dst[i][0]; a[j][6] = -src[i][0] * dst[i][1]; a[j][7] = -src[i][1] * dst[i][1]; b[i] = dst[i][0]; b[j] = dst[i][1]; } var x = numeric.solve(a, b); // matrix3d is homogenous coords in column major! // the z coordinate is unused var m = [ x[0], x[3], 0, x[6], x[1], x[4], 0, x[7], 0, 0, 1, 0, x[2], x[5], 0, 1 ]; return "matrix3d(" + m.join(',') + ')'; } // Collect the four corners by user clicking in the corners: var points = []; // map flatten the array $('.point').each(function() { var {left, top} = $(this).position(); points.push([left, top]); }); transform_terminal(); $('.point').each(function(i) { var drag = false; var [container] = $('.laptop'); var $point = $(this).mousedown(function() { drag = true; }); $(document).on('mouseup', function() { drag = false; }).on('mousemove', function(event) { if (drag) { var box = container.getBoundingClientRect(); var x = event.clientX - box.left; var y = event.clientY - box.top; points[i] = [x, y]; $point.css({ left: x, top: y }); transform_terminal(); } }); }); function transform_terminal() { var w = gemetry.width + 20, h = gemetry.height + 20; var transform = compute_transform( [ [0, 0], [w, 0], [w, h], [0, h] ], points ); $('.output pre').html(` .terminal { transform: ${transform}; } `.trim()) term.css({ '--transform': transform }); }

查看 Action 中的演示

我用它来将 jQuery Terminal 匹配到笔记本电脑的图像中。


0
投票
W3C 工作草案

在这里您可以找到有关转换的更多详细信息 但正如我所说,如果没有图像(书)的详细信息,我不知道如何计算精确的坐标。 唯一可以帮助你的是

css视角

。 我做了一个 Plunk,你可以用它来看看它的样子。 我唯一改变的是:

.container { position: relative; width: 500px; height: 500px; perspective: 500px; }

.transform{ -webkit-transform: rotateX(19deg) rotateZ(6deg) rotateY(0deg) scaleY(0.85) scaleX(0.85) skewY(0deg) skewX(-8deg) translateY(-10px) translateX(33px); -webkit-transform-origin: 50% 0% 0; transform: rotateX(19deg) rotateZ(6deg) rotateY(0deg) scaleY(0.85) scaleX(0.85) skewY(0deg) skewX(-8deg) translateY(-10px) translateX(33px); transform-origin: 50% 0% 0; }

照片与相框不完全吻合,因为它是正方形的,但是
我希望这对您有进一步的帮助。

© www.soinside.com 2019 - 2024. All rights reserved.