🐴

OpenCV.js で画像を縦に結合する

2023/05/23に公開3

ウマの某ツールが残念なことに公開停止になってしまったので、それまで開発していた自作ツールを引っ張り出してきて、せっかくだから OpenCV.js 化しようといろいろ勉強を始めた。

とりあえず、複数の画像ファイルをシンプルに縦に結合するコードが以下。

画像幅が異なる場合、一つ目の画像に合わせるようにアスペクト比を維持したまま拡大縮小する。

長々と書いてしまったが、要点は run() 関数。

OpenCV.js には「結合する」ような単純な関数は無いようで、結合後の領域を持った画像を準備し、そこに貼り付け先座標をズラしながら元画像群をコピーする、とするようだ。

下の stackblitz でお試し可能。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>OpenCV.js Concat Images(Vertical)</title>
    <script
      async
      src="https://docs.opencv.org/master/opencv.js"
      onload="initialize();"
      type="text/javascript"
    ></script>
  </head>
  <body>
    <h3>OpenCV.js Concat Images(Vertical)</h3>

    <div style="display: flex; flex-direction: column; gap: 4px">
      <div style="display: flex; width: 100%">
        <div style="display: flex; flex-direction: column; flex: 1">
          <input
            multiple
            type="file"
            id="inputImage"
            onchange="loadImageFromIdAsync('inputImage');"
          />
        </div>
      </div>

      <button onclick="run()">RUN!</button>

      <div
        id="logs"
        style="display: flex; flex-direction: row; gap: 4px; flex-wrap: wrap"
      ></div>
    </div>

    <script type="text/javascript" src="script.js"></script>
  </body>
</html>

script.js

function initialize() {
  if (typeof cv === 'undefined') {
    setTimeout(initialize, 50);
    return;
  }
  console.log('OpenCV.js is ready.');
}

const sources = [];
const destructions = [];

async function loadImageFromIdAsync(inputId) {
  const inputImage = document.getElementById(inputId);
  for (const file of inputImage.files) {
    const img = await loadImageFromFileAsync(file);
    sources.push(img);
    addImage(img, '画像');
  }
}

function loadImageFromFileAsync(file) {
  return new Promise((resolve) => {
    const img = new Image();

    img.src = URL.createObjectURL(file);
    img.onload = () => {
      const mat = cv.imread(img);
      destructions.push(mat);
      resolve(mat);
    };
  });
}

let no = 1;
function addImage(image, label) {
  const divLogs = document.getElementById('logs');
  const div = document.createElement('div');
  div.style.cssText = `display: flex; flex-direction: column; width:250px; margin-bottom: 5px;`;

  const span = document.createElement('span');
  span.innerText = `${no}. ${label ?? ''}`;
  div.appendChild(span);

  const canvas = document.createElement('canvas');
  canvas.style.cssText = `width:100%; border: 1px solid black`;
  div.appendChild(canvas);

  divLogs.appendChild(div);
  cv.imshow(canvas, image);
  no++;
}

function run() {
  const type = sources[0].type();
  const maxCol = sources.reduce((pre, cur) => Math.max(pre, cur.cols), 0);

  const resizedSources = sources.reduce((resizedSources, src) => {
    const scale = Math.max(1, maxCol / src.cols);
    const newHeight = Math.round(src.rows * scale);
    const newWidth = Math.round(src.cols * scale);
    const resizedSrc = new cv.Mat();
    destructions.push(resizedSrc);

    cv.resize(
      src,
      resizedSrc,
      new cv.Size(newWidth, newHeight),
      0,
      0,
      cv.INTER_LINEAR
    );

    resizedSources.push(resizedSrc);

    return resizedSources;
  }, []);

  const joinHeight = resizedSources.reduce((pre, cur) => pre + cur.rows, 0);

  const dst = new cv.Mat(joinHeight, maxCol, type);
  destructions.push(dst);

  let rowInc = 0;
  for (src of resizedSources) {
    src.copyTo(dst.rowRange(rowInc, rowInc + src.rows));
    rowInc += src.rows;
  }

  addImage(dst, 'RESULT');

  for (d of destructions) {
    d.delete();
  }
}

Discussion

kumakuma

初めまして。大学4年生の卒業制作にてこちらのコードを参考にさせていただいています。
もし可能でしたら縦の連結ではなく横の連結ではどこを変えれば良いか教えていただけないでしょうか?
かなりの時間調べたのですが、知識が乏しくわからないためお忙しいとは思いますがよろしくお願いいたします。

amay077amay077

横(幅),列 → width, col
縦(高さ),行 → height, row

なので、プログラムの各所の width←→height, col←→row を入れ替えてあげたら横方向の連結になると思います。

試してはいませんが、とりあえず、

src.copyTo(dst.rowRange(rowInc, rowInc + src.rows));

rowRangecolRange にして引数をちょっと変えたら横方向の少しズレた位置に画像が表示されるのではないかと思います。

卒業制作がんばってください。

kumakuma

ご丁寧にお返事ありがとうございます。
参考にさせていただき卒業制作に取り組みたいと思います。
ありがとうございました。