브라우저 환경에서 이미지를 데이터로 활용하기
브라우저 환경에서 서버에서 받아온 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);
};
사용할 이미지 불러오기 및 압축
이미지를 불러온 후 OffscreenCanvas API를 이용하여 ImageData를 추출하고 1/4로 압축합니다.
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로 압축
);
})
);
//...
이미지 데이터는 작은 이미지라도 가로 크기(width) * 세로 크기(height) 개수만큼의 픽셀 정보(Raster)를 가지고있기때문에 자바스크립 메인스레드에서 연산하기 부담스러운 크기를 가집니다.
예제에서는 압축을 통해 사이즈를 줄이지만 웹 워커등을 통해서 다른 스레드에서 연산을 수행하는 등 다양한 최적화 방법이 존재합니다.
이미지 데이터를 비교하여 파란색 영역의 비율 구하기
이미지 전체 데이터 영역에서 색상정보가 없는 부분을 제외한 색상(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%
// ...
이미지 데이터를 비교하여 파란색영역을 합성하기
결과물
위와 같이 브라우저에서 Canvas를 활용하여 이미지를 데이터화시켜 조작 및 계산이 가능했습니다. 이 과정은 실제로 AI 모델에서 수행하는 영역 분할(Segmentation) 이미지를 실시간으로 통신하여 시각화하는 과정에서 사용된 방식입니다.
AI 모델은 이미지 내의 객체나 영역을 분할(Segmentation)하여 각 영역을 구분합니다. 이러한 Segmentation 결과를 브라우저로 전송하고 시각화할 때, 이미지 데이터의 크기와 연산 부담이 문제가 될 수 있습니다.
따라서 본 예제에서는 데이터 압축을 통해 이미지 픽셀의 일부 정보를 손실시키더라도 데이터 크기를 줄여서 효율적으로 처리했으며, 이외에도 실시간 통신을 위해서 웹 워커(Web Worker)를 사용하여 메인 스레드(Main Thread)에서 연산을 분리하는등의 브라우저 성능 저하를 최소화 할 수 있는 다양한 최적화 방법이 존재합니다.
이러한 접근 방식은 실시간 시각화가 필요한 다양한 사례에 유용하게 사용할 수 있을 것이라고 생각이됩니다.
다음 포스팅에서는 손실된 색상 이미지 픽셀에 대한 보간법을 다루어, 압축으로 인한 품질 저하를 최소화하는 방법을 소개하겠습니다.