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

时间:2016-03-17 02:20:50

标签: css3 transform css-transforms

情况:

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

问题:

我无法使用手动方法精确匹配两者,即输入旋转值并观察它的作用。

理想解决方案#1

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

理想解决方案#2

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

此问题的真正解决方案

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

我为你准备了JSFiddle演示: Demo



/* 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>
&#13;
&#13;
&#13;

3 个答案:

答案 0 :(得分:8)

如果您可以使用3-d变换(例如rotateZ),那么您还可以提供matrix3d,您可以根据所需的点对应关系进行计算。

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

我使用numeric.js求解一组4个线性方程,找到将src转换为dst的透视变换矩阵。这与getPerspectiveTransform in OpenCV中的数学基本相同。

计算的2-d透视变换是使用homogeneous coordinates的3x3矩阵。 CSS matrix3d是使用齐次坐标的4x4矩阵,因此我们需要为z轴添加标识行/列。此外,matrix3d以列主要顺序指定。

获得matrix3d后,您只需将其粘贴到样式表中即可。但请记住,矩阵的计算假定(0, 0)为原点,因此您还需要设置transformOrigin: 0 0

&#13;
&#13;
// 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;
  }
});
&#13;
.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;
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:4)

我写了an answer on Math SE关于如何使用投影变换计算变换矩阵以将一个图像的角映射到四个给定坐标。它详细介绍了该计算中实际发生的情况。它还有一些CSS,我在你的示例场景中调整了the original demo

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(并建立交叉参考),请参阅Redraw image from 3d perspective to 2d

答案 2 :(得分:0)

我可以想象很难找到一个工具或公式。 如果您知道本书的角度和度量,我认为很容易计算。 W3C Working Draft在这里您可以找到有关转换的更多详细信息 但正如我所说,没有你的形象(书)的细节,我不知道你如何计算确切的坐标。

唯一可以帮助你的是 css perspective 。 我已经制作了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;
 }

照片不完全适合画面,因为它是正方形但是 我希望这会对你有所帮助。