본문 바로가기

Issues/frontend

(성능 최적화) 구강 카메라 형광 이미지 캡쳐 로직 최적화

 

일전에 작성했던 글(참고 : (트러블슈팅) 구강 카메라 형광 이미지 촬영 신호 지연 문제 사례 해결)에서, 구강 카메라의 촬영 신호를 PC의 스페이스바 이벤트로 변환해주는 백그라운드 프로그램의 감지 주기를 조정하여 촬영 신호 지연 문제를 해결한 경험이 있었습니다. 그 결과, 촬영 버튼을 누르면 브라우저에서 정확하게 2장의 이미지가 1초 간격으로 캡쳐되는 성능 개선을 이루었습니다.

 

하지만 이번에는 저사양 PC 환경에서 브라우저의 성능 저하 문제가 발생하였고, 촬영 신호가 정확하게 스페이스바 이벤트로 전달되더라도 브라우저 성능 문제나 JavaScript의 싱글 스레드 한계로 인해 1초 간격으로 정확히 두 장의 이미지를 캡쳐하지 못하는 상황이 생겼습니다. 이러한 문제는 진료 현장, 특히 치과 위생사분들에게는 매우 성가신 상황을 야기했습니다.

 

문제의 원인 분석

문제의 원인은 촬영된 이미지를 즉시 Base64로 인코딩하여 API로 보낼 객체로 변환하는 과정에 있었습니다. 기존 방식에서는 촬영 후 해당 이미지를 Base64로 인코딩하고, 이를 API로 보낼 객체 형태로 메모리에 계속해서 보관하는 방식을 사용했습니다. 하지만 Base64로 변환하는 과정이 비동기로 이루어지기 때문에, 저사양 PC나 브라우저 성능이 낮을 경우 싱글 스레드 기반의 JavaScript에서 이 작업이 지연되면서, 이미지 캡쳐가 1초 간격으로 정확히 실행되지 않는 문제가 발생했습니다.

 

특히, JavaScript는 브라우저에서 싱글 스레드로 작동하기 때문에 다른 작업들이 병렬로 수행되지 못하는 경우가 많습니다. Base64 변환 과정이 추가적인 CPU 자원을 소모하게 되고, 이로 인해 브라우저에서의 이미지 캡쳐 성능이 떨어지는 현상이 발생했던 것입니다.

 

또한, 캡쳐된 이미지 리스트를 렌더링할 때 고해상도의 원본 이미지를 사용하다 보니, 이미지 리스트의 렌더링 성능 저하도 함께 발생했습니다. 이러한 문제는 실시간으로 다수의 이미지를 다루는 구강 촬영 환경에서는 더 큰 부하로 작용했습니다.

 

해결 방법: 순서의 재정립 및 썸네일 사용

이 문제를 해결하기 위해 두 가지 접근 방법을 적용했습니다: 첫째, 이미지 캡쳐와 API 전송용 객체 변환 작업의 순서를 조정하고, 둘째, 렌더링 시 썸네일을 사용하는 방식으로 최적화를 진행했습니다.

 

1. 캡쳐 작업과 변환 작업의 분리

이미지 캡쳐 후 즉시 API 전송용 객체로 변환하지 않고, 우선 캡쳐 작업에 집중하도록 변경하였습니다. 이를 통해 비즈니스 로직의 순서를 재조정하여 동일한 기능을 유지하면서도 성능을 최적화할 수 있었습니다. 구체적인 변경 사항은 다음과 같습니다:

  • 캡쳐 작업 단순화: 촬영 직후 이미지를 Base64로 변환하거나 API 전송용 객체로 보관하지 않고, 오로지 캡쳐만 수행하도록 하여 메모리 사용을 최소화했습니다. 이를 통해 저사양 PC에서도 안정적으로 캡쳐 작업을 수행할 수 있었습니다.
  • 업로드 시점에 변환: 모든 이미지가 캡쳐된 후, 업로드 단계에서만 Base64 인코딩 및 API 전송용 객체로 변환하도록 하여 캡쳐 과정과 변환 과정을 분리했습니다. 이를 통해 실시간 캡쳐 성능에 영향을 주지 않도록 했습니다.

이를 구현하기 위해 React와 JavaScript를 활용한 몇 가지 함수들이 사용되었습니다. 예를 들어 captureFromCamera 함수는 비디오 요소로부터 이미지를 캡쳐하고 이를 데이터 URL로 변환하는 역할을 합니다. 이후 createThumbnail 함수는 고해상도 이미지를 저해상도 썸네일로 변환하여 렌더링 부하를 줄이도록 하였습니다. 이러한 함수들은 저사양 PC에서도 원활한 캡쳐 및 렌더링 성능을 보장하는 데 중요한 역할을 했습니다.

 

아래 함수는 캡쳐 후 Base64로 인코딩되지 않은 순수 이미지 데이터를 얻어내는 과정에서 사용되었습니다.

function captureFromCamera(cam: HTMLVideoElement): string | null {
  const canvas = document.createElement("canvas");
  canvas.width = cam.videoWidth;
  canvas.height = cam.videoHeight;
  const ctx = canvas.getContext("2d");

  if (ctx) {
    ctx.drawImage(cam, 0, 0, cam.videoWidth, cam.videoHeight);
    return canvas.toDataURL("image/png", 1);
  }

  return null;
}

 

 

2. 이미지 리스트에 썸네일 사용

고해상도 이미지 대신 저해상도의 썸네일을 이미지 리스트에 사용함으로써 렌더링 성능을 크게 개선했습니다. 썸네일은 촬영된 원본 이미지에서 필요한 크기로 축소하여 생성되었으며, 이는 다음과 같은 이점을 제공했습니다:

  • 렌더링 부하 감소: 리스트에 고해상도 원본 이미지 대신 작은 썸네일 이미지를 사용하여 브라우저의 메모리 사용을 줄였고, 렌더링 속도를 향상시켰습니다. 이를 통해 이미지 목록을 보다 빠르게 렌더링할 수 있었습니다.
  • 최종 이미지 필요 시 원본 사용: 썸네일은 미리보기 용도로만 사용되고, 사용자가 상세 이미지를 확인하거나 업로드할 때만 원본을 로드하도록 하여 불필요한 자원 소모를 줄였습니다.

createThumbnail 함수는 썸네일을 생성하는 데 사용되며, 비동기적으로 이미지를 처리하여 성능에 미치는 영향을 최소화하였습니다. 아래 함수는 이미지 데이터를 캡쳐한 후 이를 저해상도의 썸네일로 변환하여 렌더링 성능을 높였습니다.

function createThumbnail(
  imageDataUrl: string,
  thumbnailWidth: number,
  thumbnailHeight: number,
): Promise<string | undefined> {
  const img = new Image();
  img.src = imageDataUrl;
  return new Promise((resolve, reject) => {
    img.onload = () => {
      const canvas = document.createElement("canvas");
      canvas.width = thumbnailWidth;
      canvas.height = thumbnailHeight;
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
        resolve(canvas.toDataURL("image/png", 1));
      } else {
        reject(null);
      }
    };
    img.onerror = () => {
      reject(null);
    };
  });
}

 

 

결과 및 결론

이러한 개선을 통해 저사양 PC에서도 브라우저 성능 저하 문제를 완화할 수 있었고, 1초 간격으로 정확하게 이미지를 캡쳐할 수 있는 환경을 유지할 수 있었습니다. 또한 이미지 리스트를 렌더링할 때 고해상도의 원본 대신 썸네일을 사용함으로써 렌더링 속도 저하 문제를 해결했습니다. 그 결과 치과 위생사분들이 사용하는 진료 현장에서의 사용성을 크게 개선할 수 있었습니다.

 

저사양 PC에서의 성능 문제를 해결하기 위해서는 비동기 작업의 우선순위와 자원 사용을 적절히 관리하는 것이 중요합니다. 이번 사례에서는 Base64 변환과 같은 무거운 작업을 캡쳐 이후로 미루는 방식과, 썸네일을 활용한 렌더링 최적화를 통해 JavaScript의 싱글 스레드 한계를 극복했습니다.

 

프론트엔드 개발에서는 성능 최적화를 위해 비동기 작업의 순서를 잘 설계하는 것이 큰 차이를 만들 수 있습니다. 특히, 저사양 환경을 고려할 때 이러한 접근 방식은 더욱 중요해집니다. 이번 사례를 통해 알 수 있듯이, 동일한 기능을 수행하더라도 비즈니스 로직의 순서 변경만으로도 큰 성능 개선을 이루어낼 수 있습니다. 앞으로도 성능 최적화를 위한 다양한 방법을 시도하며, 더 나은 사용자 경험을 제공하기 위해 지속적으로 개선해 나가고자 합니다.