[refactoring]이미지 최적화(압축 및 리사이징을 곁들인🍃)
🔗 들어가며: 왜 이미지 압축 / 리사이징이 필요했을까?
퀴즈 기반 학습 서비스 구현 과정에서 LazyColumn 대부분의 item에 이미지가 들어가게 되었다. 이때 사용자가 설정한 이미지 파일 용량이 클 경우, 이미지를 저장하거나 로딩할 때 비교적 오랜 시간이 걸리는 것을 확인하였다.
다른 팀에서 진행한 서비스 QA 내용 중에서도 이미지 업로드 및 로드 속도와 관련된 내용이 존재하였다. 따라서 이미지 압축과 리사이징을 통해서 업로딩 / 로딩 속도를 개선할 필요성을 다시 한번 느끼게 되었다.
(+ 추가로 우리 서비스의 경우, 전반적인 서버 구축을 fireStore
를 활용하였다. 이때 큰 용량의 이미지 파일을 그대로 업로드할 경우, 불필요하게 많은 서버 비용이 청구될 위험이 있다. 이러한 점을 추가적으로 고려해 최종적으로 이미지 압축 및 리사이징을 적용하기로 하였다.)
🔗 이미지: 관련 개념 정리
[1️⃣ 이미지 종류: 비트맵 vs 벡터]
비트맵 방식
은 픽셀들의 집합을 통해 이미지를 형성한다. 따라서 확대할수록 이미지가 깨지는 특징을 보인다.벡터 방식
은 수학적 연산에 의한 그래픽 이미지 표현 방식이다. 따라서 사이즈와 상관없이 그래픽이 깨지지 않는다.
[2️⃣ 압축 방식 비교: 손실 vs 무손실]
- 손실: A → 압축 → 복원 → a` (원본 데이터 ≠ 복원 데이터)
- 불필요한 데이터를 삭제하는 방식으로 압축 진행
- 사용: 이미지, 동영상, 오디오 등 미디어 압축
- 무손실: A → 압축 → 복원 → A (원본 데이터 = 복원 데이터)
- 사용: 파일 압축
[3️⃣ 확장자: android bitmap compress 지원 포맷]
비트맵 이미지의 확장자는 아래 종류보다 더 다양하지만 안드로이드 bitmap compress에서 지원하는 확장자를 중심으로 비교를 해보고자 한다.
- 파일 크기
- 무손실 압축: JPEG > WEBP
- 손실 압축: PNG > WEBP
- 압축 속도
- 무손실 압축: JEPG > WEBP
- 손실 압축: PNG < WEBP
확장자 | 압축 방식 | 화질 | 투명도 지원 | 사용 |
---|---|---|---|---|
JPEG | 손실 압축 | 압축률이 높을수록 화질 손실 발생 | 알파 채널 미지원 | 웹에서 널리 사용, 사진, 복잡한 이미지, 색상 변화가 많은 이미지에 적합 |
PNG | 무손실 압축 | 원본 화질을 유지함 | 알파 채널 지원 | 투명 배경이 필요한 이미지, 텍스트 / 로고처럼 선명해야 하는 이미지 |
WEBP | 손실 / 무손실 압축 | 높은 압축률에도 상대적으로 좋은 화질을 유지 | 알파 채널 지원 | api level 30부터 deprecated됨, 아래 두 종류로 나뉨 |
확장자 | 압축 방식 | 사용 |
---|---|---|
WEBP_LOSSY | 손실 압축 | android q(api level 29)부터 제공, 저장 공간이나 대역폭이 제한적인 경우 사용 |
WEBP_LOSSLESS | 무손실 압축 | 투명 배경이 필요한 이미지, 텍스트 / 로고처럼 선명해야 하는 이미지 |
[4️⃣ 압축률이란?]
압축을 진행했을 때 데이터 크기가 원본에 비해 얼마나 많이 줄어들었냐를 나타내는 지표이다. 즉, 압축률이 높을수록 파일의 크기가 더 많이 줄어드는 것이다.
🔗 짚고 넘어가기: 압축 효율 차이가 발생하는 이유?
각 압축 포맷마다 내부적인 압축 방식이 다르기 때문에
압축 효율이 서로 다르게 된다. WEBP 포맷이 비교적 PNG, JPEG보다 최신 포맷이기 때문에 전반적인 압축 효율이 더 좋다. (실제로 공식 문서를 확인해본다면 WEBP가 동일한 화질에 대해 PNG보다는 26% 적은 용량을, JPEG보다는 25 - 34% 적은 용량을 제공한다고 나와 있다.)
그렇다면 각 포맷 내부적인 압축 방식에 대해 다루기 전에 손실/무손실 압축의 경우 각각 어떤 방식으로 압축을 진행하는지에 대한 내용부터 다루고 넘어가자.
🔗 짚고 넘어가기: 손실 / 무손실 압축은 어떤 방식으로 압축이 진행될까?
손실 압축의 경우, 압축 후 해제를 할 때 원본 데이터가 손실된다. 이때 사람들이 이미지를 확인할 때 덜 민감한 정보들(ex: 색상 정보, 고주파 영역)이 손실의 대상이 된다. 이 부분은 추후 각 이미지 포맷 압축 방식을 다룰 때 조금 더 자세히 다뤄보고자 한다.
그렇다면 무손실 압축의 경우에는 원본 데이터가 유지가 되는데 어떻게 압축이 진행되는 걸까? 무손실 압축에서는 정보를 표현하는 방식이 최적화된다. 데이터의 중복을 조금 더 효과적으로 표현하거나 재배열을 하는 등의 방식이 적용이 되어 원보 데이터가 손실되지 않고도 이미지 용량을 줄일 수 있게 된다.
그럴다면 무손실 / 손실 압축이 전반적으로 어떤 데이터를 줄이는지에 대해 다뤄보았다. 이제는 각 압축 포맷 내부에서의 압축 방식에 대해 조금 더 자세히 다뤄보자.
🔗 짚고 넘어가기: 무손실 압축 방식 톺아보기
무손실의 경우, 데이터 표현 방식 최적화
를 통해 압축을 진행한다. 따라서 PNG와 JPEG는 모두 다음 두 가지 과정을 통해 압축을 진행한다.
- 중복되는 데이터가 많도록 전처리 과정을 진행한다.
- 중복되는 데이터를 부호화 알고리즘을 통해 최적화한다.
전반적인 과정은 동일하지만 세부적인 과정에서는 차이가 발생한다.
[1️⃣ PNG 압축 방식]
PNG는 크게 과정이 filtering -> deflate
로 진행되게 된다.
filtering
: 추후 활용할 반복되는 패턴이 최대한으로 많도록 이미지 전처리를 진행한다.deflate
: LZ77과 허프만 부호화가 결합된 알고리즘 방식으로 반복되는 패턴을 효과적으로 압축한다.- LZ77: 슬라이딩 윈도우를 활용해 과거 문자열을 참조해 반복되는 문자열을 효과적으로 줄인다.
- 허프만 부호화: 문자의 등장 빈도에 따라 다른 길이의 부호를 사용하여 암호화를 진행한다.
[2️⃣ WEBP 압축 방식]
WEBP의 압축 효율을 높이기 위해 예측 코딩
이 활용된다. 예측 코딩이란 상하좌우 픽셀값을 바탕으로 현재 픽셀값을 예측하고 실제 값과의 차이만을 기록해 데이터를 효과적으로 압축한다.
그리고 WEBP의 무손실 압축은 PNG의 무손실 압축 방식과 전반적인 과정은 동일하다. 하지만 전처리 과정이 조금 더 세밀하게 진행이 된다.
찾아본다면 다양한 세밀한 방식이 존재하지만 여기서는 몇 가지만 다루고자 한다. 핵심은 전처리 과정이 더욱 세밀하게 이루어져 PNG보다 압축 효율이 조금 더 좋아진다는 점이다.
- 자주 사용되는 색상 정보를 로컬 팔레트로 기록해 이를 활용해 색상 정보를 index로 효과적으로 표현
- RGB 중 사람들이 가장 민감한 G 색상을 가지고 R, B 색상 압축
🔗 짚고 넘어가기: 손실 압축 방식 톺아보기
손실 압축 역시 JPEG, WEBP 모두 비슷한 과정을 거친다. 우선은 비슷한 전반적인 과정을 먼저 다루고 세부적인 차이를 다뤄보다. 우선은 손실 압축은 크게 다음의 세 가지 과정을 거친다.
- 크로마서브샘플링으로 색상 정보 압축
- DCT -> 양자화를 통해 고주파 영역 압축
- 부호화를 통해 데이터 표현 최적화
[1️⃣ 크로마서브샘플링]
크로마서브샘플링은 사람의 눈이 색상보다는 밝기에 민감하다는 것을 활용해 밝기 정보와 색상 정보를 분리하고 색상 정보를 밝기보다 낮은 해상도로 압축하게 된다.
우선 기존 RGB
중심으로 표현된 색상 정보를 YCrCb
형태로 표현한다. 이때 Y는 밝기, Cr/Cb는 각각 색 정보를 나타내게 된다. 따라서 이렇게 밝기와 색 정보를 분리함으로써 효과적으로 색 정보를 압축할 수 있게 된다.
[2️⃣ DCT -> 양자화]
해당 부분을 학습했을 때 말이 너무 어려워 잘 이해가 가지 않았다.🥲 하지만 필자가 이해한 바로는 핵심은 다음과 같다.
우선 이 과정이 진행되는 이유는 이미지에서 사람의 눈이 고주파 영역보다는 저주파 영역에 민감하다는 점을 활용해 압축을 효과적으로 진행하기 위함이다.
저주파
: 이미지 내 밝기가 거의 변하지 않는 영역으로 이미지의 전반적인 구조와 톤을 결정하는 부분이다.고주파
: 이미지 픽셀 내 밝기 차이가 극명하게 나는 영역으로 고주파 영역으로 인해 이미지에서 경계나 모서리 부분의 세세한 부분과 관련이 있다.
따라서 DCT를 통해 이미지의 주파수 분리를 진행하고, 양자화를 통해 고주파 영역의 명도를 낮추며 압축이 진행된다.
[3️⃣ 부호화]
무손실 압축과 마찬가지로 데이터 표현 최적화를 진행하는 부분이다.
[4️⃣ 그렇다면 JPEG 손실 vs WEBP 손실?]
우선은 WEBP는 무손실 압축에서 설명했듯이 예측 코딩
을 통해 압축 효율을 높인다. 여기서 나아가 부호화 과정에서도 JPEG는 허프만 부호화
를 사용하며, WEBP는 허프만보다 조금 더 효율적인 산술 부호화
를 사용한다.
-
산술 부호화
- 다른 부호화 알고리즘은 각각의 기호를 1:1 부호로 대체하는 반면 산술 부호화는 전체 메시지를 하나의 실수 n으로 대체하게 된다.
🔗 그렇다면 각각 언제 활용할 수 있을까?
이미지 포맷의 장단점에 대해 학습을 한 후, 이미지 압축을 진행할 때 어떠한 압축 포맷을 활용할지에 대해 고민하는 기준이 생기게 되었다.
크게 호환성, 압축 방식(손실 / 무손실), 투명도 지원을 중심으로 고려해볼 수 있다.
[1️⃣ 호환성]
호환성 역시 압축 방식을 고를 때 고려해야 하는 중요한 요소 중 하나이다. 만약 호환성을 중요하게 생각한다면 WEBP와 같은 최신 포맷보다는 PNG와 JPEG를 활용하는 것이 더욱 적합하다.
[2️⃣ 압축 방식(손실 / 무손실)]
또한, 데이터 손실을 허용하느냐 아니냐에 따라 손실 및 무손실 압축을 적용할 수 있다.
[3️⃣ 투명도 지원]
마지막으로 투명도를 지원해야 할 경우, JPEG를 활용할 수 없다. JPEG는 알파 채널을 지원하지 않기 때문에 알파 채널을 지원하는 PNG 혹은 WEBP를 고려해볼 수 있다.
🔗 적용: 이미지 압축 비교 결과
다음의 노션 페이지를 본다면 우리 팀에서 진행한 확장자 및 압축률에 따른 압축 시간과 용량 차이를 확인할 수 있다.
- 파일 크기: JPEG » WEBP
- 압축 속도: JPEG « WEBP
우리 팀은 용량을 최소화해 파일 업로드 및 로드 시간을 줄이고, 서버 비용을 최소화하는 것이 최종 목표였다. 용량 최소화를 위해 우선 무손실 압축 방식인 PNG는 비교에서 제외하였다. 또한, 압축 속도보다 파일 크기에 우선순위를 더 크게 두어 최종적으로 WEBP
를 활용해 이미지 압축을 진행하였다.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
resizedBitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, 60, baos)
} else {
resizedBitmap.compress(Bitmap.CompressFormat.WEBP, 60, baos)
}
WEBP는 위에서 확인했듯이 손실 / 무손실 두 가지 방식 모두 지원한다. 우리 서비스의 대부분의 이미지는 프로필 이미지기 때문에 약간의 화질 저하가 존재해도 괜찮다. 따라서 WEBP_LOSSY
를 적용해 용량을 줄일 수 있도록 코드를 작성하였다. WEBP_LOSSY
의 경우, Android Q 이상부터 지원하기 때문에 버전에 따른 처리를 진행해줬다.
그리고 위 코드에서 설정하는
60
의 경우, 압축률이 아닌quality
를 의미한다. 즉, 60%의 이미지 quality를 유지하도록 압축을 진행하는 것이다. 압축률과 헷갈리지 않도록 주의하자.
🔗 적용: 이미지 리사이징
다음의 노션 페이지를 본다면 우리 팀에서 진행한 리사이징에 따른 용량 및 화질 차이를 확인할 수 있다.
리사이징 역시 비트맵에서 제공하는 함수를 통해 손쉽게 적용이 가능했다. 우리 팀의 경우, 디스플레이 해상도 기준들을 참고해 리사이징을 진행하였다.
해상도 | 사이즈 |
---|---|
HD | 1280 x 720 |
FHD | 1920 x 1080 |
QHD | 2560 x 1440 |
UHD | 3840 x 2160 |
우리 팀에서는 압축 시간 대비 화질이 가장 좋은 사이즈를 확인해보았고, 이미지 width가 2560이 넘어갈 때 2560을 기준으로 리사이징을 하는 것을 최종적으로 결정하였다.
모바일 디바이스 중에서 가로가
2560
을 넘어가는 경우가 과연 많을까?멘토님께서 해당 부분에 대해 다음과 같은 의문을 제시해주었다. 대부분의 모바일 디바이스의 width는 2560을 넘어가지 않는다. 그렇기 때문에 2560을 기준으로 한다면 리사이징이 큰 의미가 없을 것 같다는 판단을 하였고, 추후 해당 사이즈를 수정할 예정이다.
🔗 마무리하며
최종적으로 우리팀은 WEBP 60을 통해 이미지 압축을 진행하였고, 가로 사이즈가 2560을 넘어갈 경우 리사이징 하는 방식으로 이미지 최적화를 진행하였다. 그 결과, 이미지 업로드 및 로딩하는 시간이 4초 -> 2초로 줄어드는 결과를 얻을 수 있었다.
이미지 압축 및 최적화 하는 방식은 생각보다 간단했다. 오히려 서비스의 특성에 맞게 적절한 압축방식과 리사이징을 고민하는 과정에 더 오랜 시간이 필요했던 것 같다. 이번 계기를 바탕으로 각 이미지 확장자의 차이에 대해 명확히 짚고 넘어갈 수 있었다. 이 경험은 추후 서비스 특성에 맞춰 올바른 확장자를 판단하는데 큰 도움이 될 것 같다!
댓글남기기