【问题标题】:How to match 3D perspective of real photo and object in CSS3 3D transforms如何在 CSS3 3D 变换中匹配真实照片和对象的 3D 透视图
【发布时间】:2021-10-28 00:24:35
【问题描述】:

情况:

在我的特定情况下,我有 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>

【问题讨论】:

    标签: css transform css-transforms


    【解决方案1】:

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

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

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

    计算得到的二维透视变换是一个使用homogeneous coordinates 的 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>

    【讨论】:

    • 哇,伙计!你超出了我的预期。谢谢你。赏金是你的。
    • 第二次看它完全不能解决我的问题,因为它可能会破坏照片的纵横比,但我认为它可以通过另一层封装来解决。 - 你可以编辑你的答案来解决这个问题。
    • 从技术上讲,透视变换不会保留纵横比,但有一种方法可以检查它是否保留。如果无法匹配宽高比,您还必须决定如何剪辑图像。
    • 出色的答案。 (我花了很长时间才找到!)
    • 很好的答案!我知道它有点老了,但是......它仍然可以完美运行,并且会在我的项目中帮助我!
    【解决方案2】:

    我在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

    【讨论】:

    • 谢谢。您的视觉工具比公认的答案更好。如果你完善你的答案,使其与我的问题相匹配,我会重新考虑接受的答案。
    • 您希望以何种方式改进以更好地匹配您的问题?有一个工具可以简单地拖动角落(以我的演示的形式),适应书面输入应该很容易,并且数学解释在其他帖子中,所以我认为我不应该复制所有再说一遍。您希望看到它应用于您的示例吗?
    • 我希望任何遇到我问题的人都能得到明确的答案。先前接受的答案帮助我解决了我的问题。你的有更好的演示。如果你把它们结合起来,我会很高兴..
    • @Marakoss:我不希望将我的答案与接受的答案结合起来,因为我不会像 szym 那样做事。在没有第三方库帮助的情况下求解 4×4 方程组非常乏味,在这里完全可以避免。右下角有 1 意味着如果左上角最终为无穷大,您将遇到麻烦。如果你直接拖动角落就不会发生,但我仍然对 opencv 以这种方式做事感到惊讶,因为它使代码不像它可能的那样通用。我已经修改了我的演示,尽管我认为我的 MSE 帖子比这更有价值。
    • 这太棒了!代码只是救了我的命,但我想知道..是否也可以添加跨度?我的意思是,不仅要拉伸/倾斜图像,还要移动它。
    【解决方案3】:

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

    唯一可以帮助您的是 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;
     }
    

    照片不完全适合框架,因为它是一个正方形,但 希望对您有所帮助。

    【讨论】:

    • 嗨。感谢你的付出。不幸的是,我不能接受您的答案,因为它不是普遍的答案,处于相同情况的其他人无法自己弄清楚如何做到这一点。
    【解决方案4】:

    基于@szym 代码,我为我的需要创建了一个可以改进的演示。该代码使用可拖动的点,因此更容易找到点并计算矩阵。矩阵也打印在左侧。

    // 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
        });
    }
    

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

    【讨论】:

      猜你喜欢
      • 2023-03-14
      • 2012-07-28
      • 2011-07-27
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      • 1970-01-01
      相关资源
      最近更新 更多