React + canvas로 열화상 영상 실시간 렌더링하기
실시간으로 전달되는 열화상 카메라의 영상 데이터를 웹 브라우저 상에서 시각화해야 했습니다. 영상은 base64 형태의 JPEG 이미지를 HTML5 <canvas> 요소에 렌더링하는 방식으로 구현했습니다. 이 글에서는 해당 과정 중 영상 처리 및 시각화 로직을 정리해보려고 합니다.
개발 환경
- Node v22.16.0
- React v18.3.1 (JavaScript) + CRA
개발 순서
- 기본 구조 확인하기
- 영상 데이터 렌더링 준비하기
- 컬러맵 적용하기 (흑백 → 컬러)
- 최고/최저 온도 위치 표시하기
1. 기본 구조 확인하기 (표준적인 방식)
서버에서 전달받은 이미지 데이터는 base64 형식의 JPEG입니다.
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = `data:image/jpeg;base64,${base64}`;
img.onload = () => {
ctx.drawImage(img, 0, 0, width, height);
};
img.onload 를 사용하는 이유
new Image()은 비동기로 이미지를 로드하기 때문에 (img.onload)가 꼭 필요합니다. img 객체는 내부적으로 JPEG을 디코딩해서 HTMLImageElement로 만든 후 drawImage()로 캔버스에 픽셀 복사합니다.
2. 영상 데이터 렌더링 준비하기
브라우저 캔버스는 drawImage()로 이미지를 바로 그릴 수 있지만, 픽셀을 조작하려면 getImageData로 복사해서 수동 수정해야 합니다. 컬러맵 적용, 온도 마커, 텍스트 표시 등은 픽셀 단위 연산 또는 오버레이 그리기이기 때문에, 메인 캔버스에 직접 하기보단 bufferCanvas에서 먼저 처리하고, 마지막에 mainCanvas.drawImage(bufferCanvas) 합니다.
img.onload = () => {
const bufferCanvas = document.createElement('canvas');
bufferCanvas.width = width;
bufferCanvas.height = height;
const bufferCtx = bufferCanvas.getContext('2d', { willReadFrequently: true });
bufferCtx.drawImage(img, 0, 0, width, height);
// 흑백/컬러 모드 기능 구현
// .
// .
// .
// 온도 마커 기능 구현
// .
// .
// .
requestAnimationFrame(() => {
ctx.drawImage(bufferCanvas, 0, 0, width, height);
bufferCtx.clearRect(0, 0, width, height);
});
}
requestAnimationFrame을 사용하는 이유
후처리까지 마친 이미지를 실제 캔버스에 그릴 때, requestAnimationFrame()을 사용하면 브라우저의 렌더링 타이밍에 맞춰
최적의 순간에 이미지를 출력할 수 있습니다. 이 방식은 일반적인 drawImage() 호출보다 더 부드럽고 깜빡임이 적으며, CPU와 GPU 사용 면에서도 효율적입니다.
3. 컬러맵 적용하기 (흑백 → 컬러)
열화상 이미지 데이터는 보통 흑백이기 때문에, 시각적으로 온도를 더 잘 구분할 수 있도록 컬러맵을 적용했습니다. 이를 위해 전체 영상 데이터인 imageData.data를 조작합니다. 이 배열은 픽셀의 RGBA 값을 1차원으로 나열한 형태이며, data[i]를 기준으로 getColorMap(gray)을 통해 대응하는 RGB 색상을 찾아 적용합니다.
const imageData = bufferCtx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = data[i];
const [r, g, b] = getColorMap(gray);
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
}
bufferCtx.putImageData(imageData, 0, 0);
4. 최고/최저 온도 위치 표시하기
서버에서 픽셀 단위의 온도 배열도 함께 전달되므로, 그 중 최고/최저 온도가 찍힌 위치에 마커를 그릴 수 있습니다.
const buffer = temperatureData.buffer;
let min = Infinity, max = -Infinity, minIdx = 0, maxIdx = 0;
for (let i = 0; i < buffer.length; i++) {
const temp = buffer[i];
if (temp < min) { min = temp; minIdx = i; }
if (temp > max) { max = temp; maxIdx = i; }
}
const minX = minIdx % width, minY = Math.floor(minIdx / width);
const maxX = maxIdx % width, maxY = Math.floor(maxIdx / width);
bufferCtx.fillStyle = 'blue';
bufferCtx.fillRect(minX - 1, minY - 1, 3, 3);
bufferCtx.fillStyle = 'red';
bufferCtx.fillRect(maxX - 1, maxY - 1, 3, 3);
5. 실행 결과
'개발 기록 > [CTSF] 25.05.12-' 카테고리의 다른 글
[성능 최적화] Base64 디코딩 성능 최적화 (0) | 2025.06.17 |
---|