브라우저 환경에서 이미지를 데이터로 활용하기

브라우저 환경에서 서버에서 받아온 Image를 통해 데이터로 활용했던 기록

브라우저 환경에서 이미지를 데이터로 활용하기

개요

브라우저에서 이미지를 ImageData API 통해서 데이터화하고 참조 및 합성등에 대한 내용에 대해서 다룹니다.
본문에서는 같은 비율에 베이스 이미지 위에 수행된 컬러맵 이미지가 어느정도 비율을 차지하는지에 대한 결과값을 계산하고 이미지를 합성조작하여 시각화합니다.

진행과정

  • 베이스 이미지 및 파란색 컬러맵 이미지를 OffScreencanvas API 통하여
    ImageData로 변환
  • 이미지 데이터 연산을 위해서 변환된 이미지 데이터에 대해서 데이터 압축수행
  • 각 압축된 이미지 데이터를 비교하여, 베이스 이미지 위에 얼마나 컬러맵이 존재하는지 비율 계산
  • 두 이미지 데이터를 참조하여 이미지를 합성 후 시각화

먼저 코드에서 사용할 기능 함수를 미리 정의합니다.

// 이미지 로드 함수
const loadImage = (src) => {
    return fetch(src)
        .then((r) => {
        if (!r.ok) throw new Error("Network response was not ok");

        return r.blob();
        })
        .then((blob) => createImageBitmap(blob));
};

// 이미지 데이터 압축 함수
const getCompressedImageData = (imageData, scale) => {
    const compressSize = Math.floor(1 / scale);
    const compressWidth = Math.ceil(imageData.width / compressSize);
    const compressHeight = Math.ceil(imageData.height / compressSize);

    const tempData = new Uint8ClampedArray(
        compressWidth * compressHeight * 4
    );

    for (let y = 0; y < compressHeight; y++) {
        for (let x = 0; x < compressWidth; x++) {
          const index = y * compressWidth * 4 + x * 4;
          const rgba = imageData.data.slice(
              (y * compressSize * imageData.width + x * compressSize) * 4,
              (y * compressSize * imageData.width + x * compressSize) * 4 + 4
          );
          
          tempData.set(rgba, index);
        }
    }

    return new ImageData(tempData, compressWidth, compressHeight);
};

사용할 이미지 불러오기, 이미지 데이터 압축 함수 정의

이미지 불러오기 및 압축

💡
이미지 데이터는 작은 이미지라도 가로 크기(width) * 세로 크기(height) 개수만큼의 픽셀 정보(Raster)를 가지고있기때문에 자바스크립트에서 대부분의 경우 메인스레드에서 연산하기 부담스러운 크기를 가집니다.
예제에서는 데이터 압축을 통해서 최적화를 하지만 웹 워커를 사용하여 다른 스레드에서 연산작업을 수행하는 등에 다양한 최적화 방법이 존재합니다.
const baseImageSource = `https://w-log.dev/content/images/2024/05/baseImage-1.png`;
const colorImageSource = `https://w-log.dev/content/images/2024/05/colorImage-1.png`;

const offCanvas = new OffscreenCanvas(640, 360);
const ctx = offCanvas.getContext("2d");

const [baseImageData, colorImageData] = await Promise.all(
    [baseImageSource, colorImageSource].map(async (src) => {
        const bitmap = await loadImage(src);
        offCanvas.width = bitmap.width;
        offCanvas.height = bitmap.height;
        ctx.clearRect(0, 0, 9999, 9999);
        ctx.drawImage(bitmap, 0, 0);

        return getCompressedImageData(
            ctx.getImageData(0, 0, bitmap.width, bitmap.height),
            0.25 // 이미지 데이터를 1/4로 압축
        );
    })
);

//...

이미지를 불러온 후 OffscreenCanvas API를 이용하여 ImageData를 추출하여 1/4 압축을 수행합니다.

이미지 데이터를 비교하여 파란색 영역의 비율 구하기

이미지 전체 데이터 영역에서 색상정보가 없는 부분을 제외한 색상(blue)영역의 비율을 계산합니다.

// 영역 비율 계산함수
const calculateFillArea = (baseImageData, colorImageData) => {
  let total = 0;
  let progress = 0;

  for (let y = 0; y < baseImageData.height; y++) {
    for (let x = 0; x < baseImageData.width; x++) {
      const index = (y * baseImageData.width + x) * 4;
      const baseRgba = baseImageData.data.slice(index, index + 4);
      const colorRgba = colorImageData.data.slice(index, index + 4);

      if (baseRgba[3] === 255) total += 1;
      if (baseRgba[3] === 255 && colorRgba[3] === 255) progress += 1;
    }
  }

  return ((progress / total) * 100).toFixed(2) + "%";
};

const fillPercentage = calculateFillArea(baseImageData, colorImageData); // 49.56%

// ...

이미지 데이터를 참조하여 영역비율을 계산하는 코드

이미지 데이터를 비교하여 파란색영역을 합성하기

// 이미지 합성 함수
const compositeImageData = (baseImageData, colorImageData) => {
  const tempData = new Uint8ClampedArray(
    baseImageData.width * baseImageData.height * 4
  );

  for (let y = 0; y < baseImageData.height; y++) {
    for (let x = 0; x < baseImageData.width; x++) {
      const index = (y * baseImageData.width + x) * 4;
      const baseRgba = baseImageData.data.slice(index, index + 4);
      const colorRgba = colorImageData.data.slice(index, index + 4);

      if (baseRgba[3] === 255 && colorRgba[3] === 255) {
        tempData.set(colorRgba, index);
      } else {
        tempData.set(baseRgba, index);
      }
    }
  }

  return new ImageData(
    tempData,
    baseImageData.width,
    baseImageData.height
  );
};

const compositedImageData = compositeImageData(baseImageData, colorImageData);

canvas.width = compositedImageData.width;
canvas.height = compositedImageData.height;
canvas.getContext("2d").putImageData(compositedImageData, 0, 0);

//...

이미지 데이터를 조작하여 합성하는 코드

결과물

결과화면

완성된 결과물 코드

위와 같이 브라우저에서 Canvas를 활용하여 이미지를 데이터화시켜서 조작 및 계산이 가능했습니다.
이 과정은 실제로 AI 모델에서 수행하는 Segmentation 이미지를 실시간으로 통신하여 시각화하는 과정에서 사용했던 방식입니다.
압축을 통해서 이미지 픽셀이 손실되었지만 웹 워커와 함께 데이터를 작게 유지함으로서 브라우저에서 실시간으로 10~15프레임 성능을 유지하면서 데이터를 렌더링할 수 있었습니다.
이어지는 주제로 손실된 색상 이미지 픽셀에 대한 보간법에 대해서 포스팅하도록 하겠습니다.