이미지 처리
고친 사람 github-actions[bot]
이미지 처리는 사진을 받아 계산으로 고친 사진을 내놓는 일입니다. 흐린 사진을 또렷하게 만드는 일이 그 예입니다. 컴퓨터에게 사진은 밝기를 적은 숫자들의 격자입니다. 그래서 사진을 고치는 일은 그 숫자를 바꾸는 계산이 됩니다.
쉽고 빠른 이해
무슨 일을 하나 — 사진을 받아 고친 사진을 내놓습니다. 쇼핑몰에 올라온 큰 상품 사진을 목록에 띄울 작은 사진으로 줄이는 일이 그 예입니다.
왜 하나 — 찍힌 사진은 그대로 쓰기 어려울 때가 많습니다. 너무 크거나 어둡거나 잡음이 낍니다. 사람이 편집 도구로 한 장씩 고치면 사진이 늘어나는 만큼 손이 듭니다.
어떻게 도나
- 사진 파일은 압축되어 있습니다. 이를 풀어 칸마다 밝기를 적은 숫자 격자로 되돌립니다
- 칸마다 새 값을 셈합니다. 그 칸만 보기도 하고 둘레 칸을 함께 보기도 합니다. 다른 칸의 값을 옮겨 오기도 합니다
- 새 값으로 채운 격자를 다시 사진 파일로 담습니다
대가 — 풀린 격자는 압축된 파일보다 훨씬 커서 메모리를 많이 씁니다. 칸마다 계산하니 큰 사진일수록 오래 걸립니다. 한 번 버린 정보는 되살리지 못하니 원본을 따로 둡니다.
언제 쓰나 — 사진이 많아 사람이 한 장씩 고칠 수 없을 때 씁니다. 몇 장뿐이면 편집 도구로 고치면 됩니다. 고친 사진이 아니라 「무엇이 찍혔나」라는 답을 원하면 컴퓨터 비전의 일입니다.
상세
이 절은 먼저 사진이 컴퓨터 안에서 어떤 꼴인지 봅니다. 다음은 새 값을 정하는 세 가지 계산입니다. 끝으로 이웃 분야와 갈리는 선, 서버에서 이 일을 맡을 때 정할 것을 봅니다.
예로는 쇼핑몰 판매자가 올린 상품 사진 한 장을 계속 씁니다. 실내에서 휴대폰으로 찍어 조금 어둡고 잡음이 낀 사진입니다. 서비스는 이 사진을 상품 목록에 띄울 작은 사진으로 바꾸려 합니다.
숫자 격자로 된 사진
컴퓨터 안의 사진은 작은 칸이 가로세로로 늘어선 격자입니다. 이 칸 하나를 픽셀이라고 부릅니다. 픽셀마다 그 칸의 밝기를 숫자로 적습니다.
흑백 사진은 흔히 밝기를 0 부터 255 까지의 정수로 적습니다. 0 이 검정입니다. 255 는 하양입니다. 픽셀 하나를 1바이트에 담으면 이 256 가지 값이 나옵니다.
컬러 사진은 픽셀마다 숫자 셋을 적습니다. 빨강과 초록과 파랑이 얼마나 섞였는지입니다. 이렇게 세 값으로 색을 적는 방식을 RGB(Red Green Blue, 빨강·초록·파랑)라고 부릅니다.
RGB 의 세 값 하나하나를 채널이라고 합니다. 사진을 채널로 나눠 두면 색마다 따로 계산할 수 있습니다. 빨강 채널만 키워 사진을 붉게 만드는 식입니다.
이미지 처리는 이 격자를 받아 새 격자를 내놓습니다. 상품 사진을 밝게 하는 일은 모든 픽셀의 숫자를 키우는 일입니다. 사진을 줄이는 일은 더 작은 격자를 새로 채우는 일입니다.
찍힌 사진을 손대지 않고 쓰기 어려운 까닭
찍힌 사진은 쓰려는 곳에 맞지 않을 때가 많습니다. 휴대폰 사진은 목록 화면에 띄우기에 너무 큽니다. 어두운 실내에서 찍으면 밝기가 모자랍니다.
빛이 모자라면 잡음도 낍니다. 잡음은 고른 바탕에서 픽셀 값이 들쭉날쭉 튀는 흠입니다. 사진에서는 자글자글한 점으로 보입니다.
사진이 몇 장이면 사람이 편집 도구로 고치면 됩니다. 판매자 수천 명이 날마다 사진을 올리면 그렇게 못 합니다.
고치는 방법을 계산으로 적어 두면 컴퓨터가 모든 사진에 같은 손질을 합니다. 이 계산을 짜고 고르는 일이 이미지 처리입니다. 우리말로는 영상 처리라고도 부릅니다.
새 값을 정하는 세 가지 계산
이미지 처리의 계산은 새 격자의 픽셀마다 값을 하나씩 정하는 일입니다. 계산은 그 값을 정할 때 원본의 무엇을 보느냐로 세 갈래로 나뉩니다.
| 갈래 | 새 값을 정할 때 보는 것 | 상품 사진에 쓰는 예 |
|---|---|---|
| 점 연산 | 원본의 같은 위치에 있는 픽셀 하나 | 어두운 사진 밝히기 |
| 이웃 연산 | 같은 위치의 픽셀과 그 둘레 | 잡음 지우기 |
| 기하 변환 | 원본의 다른 위치에 있는 픽셀 | 크기 줄이기 · 돌리기 |
세 갈래는 따로 쓰기보다 이어서 거는 일이 많습니다. 상품 사진이라면 잡음 지우기와 밝기 맞추기와 크기 줄이기를 한 번에 잇습니다. 아래 세 소절이 갈래마다 계산 하나씩을 따라갑니다.
픽셀 하나만 보는 점 연산
점 연산은 원본 픽셀 하나의 값만 보고 새 값을 정합니다. 둘레 픽셀은 보지 않습니다. 그래서 같은 식 하나를 모든 픽셀에 되풀이하면 끝납니다.
아래는 밝기 100 인 픽셀 하나에 흔한 점 연산 셋을 건 결과입니다. 줄 오른쪽 주석이 그 줄이 내는 값입니다. 대비는 두 값의 차이라서 아래에서 두 값으로 다시 봅니다.
const p = 100; // 원래 밝기
p + 50; // 150 · 밝히기
p * 2; // 200 · 대비 키우기
p > 128 ? 255 : 0; // 0 · 이진화
더하기는 사진 전체를 고르게 밝힙니다. 어두운 상품 사진을 손볼 때 가장 먼저 떠올리는 계산입니다.
곱하기는 값 사이의 간격을 넓힙니다. 50 과 100 은 2 를 곱하면 100 과 200 이 됩니다. 둘의 차이가 50 에서 100 으로 벌어집니다.
밝은 곳과 어두운 곳의 차이를 명암 대비라고 부릅니다. 대비가 낮은 사진은 뿌옇게 보입니다. 곱하기로 간격을 넓히면 그 뿌연 느낌이 걷힙니다.
마지막 줄의 이진화는 기준값보다 밝으면 하양, 아니면 검정으로 바꿉니다. 이 기준값을 임계값이라고 합니다. 바탕과 글자만 남기면 되는 문서 스캔에 씁니다.
셈한 값이 1바이트에 안 들어갈 때도 있습니다. 100 에 3 을 곱하면 300 이 됩니다. 1바이트에 담기는 값은 255 까지입니다.
그래서 255 를 넘는 값은 255 로 자릅니다. 0 보다 작은 값은 0 으로 올립니다. 이렇게 값을 범위 안으로 눌러 넣는 처리를 클램핑이라고 부릅니다.
잘린 픽셀은 원래 얼마나 밝았든 모두 같은 하양이 됩니다. 그 차이는 사라져서 되살릴 수 없습니다.
컬러 사진에는 같은 식을 채널마다 따로 겁니다. 세 채널에 같은 값을 더하면 사진 전체가 밝아집니다.
세 채널을 하나로 합쳐 흑백 사진으로 바꾸는 일도 점 연산입니다. 이때는 세 값을 그냥 평균 내지 않고 채널마다 다른 비율을 곱해 더합니다. 사람 눈은 같은 세기라도 초록을 가장 밝게 느낍니다. 그래서 초록에 가장 큰 비율을 줍니다.
둘레를 함께 보는 이웃 연산
이웃 연산은 새 값을 정할 때 같은 위치의 픽셀과 그 둘레 픽셀을 함께 봅니다. 흔히 가운데 픽셀을 둘러싼 3×3 칸, 곧 아홉 픽셀을 봅니다.
잡음은 점 연산으로 못 고칩니다. 어느 픽셀이 튀었는지는 둘레와 견줘야 알 수 있기 때문입니다. 픽셀 하나만 보는 계산에는 그 픽셀이 튀었다는 단서가 없습니다.
상품 사진의 바탕 한 귀퉁이를 잘라 봅니다. 고른 회색 바탕 한가운데에 잡음 하나가 하얗게 튀어 있습니다. 칸 하나가 픽셀 하나입니다. 칸에 적힌 값은 밝기입니다.
100 100 100
100 250 100
100 100 100
가운데 픽셀을 아홉 값의 평균으로 바꿔 봅니다. 셈은 아래와 같습니다.
const sum = 100 * 8 + 250; // 1050
Math.round(sum / 9); // 117
250 이던 픽셀이 117 로 내려옵니다. 튀던 픽셀이 둘레와 거의 비슷해졌습니다.
이 계산을 모든 픽셀에 되풀이하면 사진 전체가 부드러워집니다. 이렇게 평균을 내는 이웃 연산을 블러라고 부릅니다. 잡음이 줄어드는 대신 물체의 테두리도 함께 뭉개집니다.
블러처럼 이웃 연산으로 사진을 손질하는 계산을 흔히 필터라고 부릅니다. 필터를 바꿔 걸면 같은 사진을 흐리게도, 또렷하게도 만들 수 있습니다.
블러처럼 아홉 값을 곱해 더하는 필터라면, 아홉 픽셀에 곱할 비율이 필터마다 다릅니다. 그 비율을 3×3 격자에 적어 둔 것을 커널이라고 합니다. 평균을 내는 커널은 아홉 칸이 모두 9분의 1 입니다.
커널의 칸 값만 바꾸면 같은 틀로 다른 손질을 합니다. 그래서 곱해 더하는 필터는 대개 어떤 커널을 쓰느냐로 이름이 붙습니다.
가운데에서 멀어질수록 비율이 종 모양으로 줄어드는 커널은 가우시안 블러를 만듭니다. 가까운 픽셀일수록 결과에 더 많이 섞입니다. 그래서 고른 평균보다 자연스럽게 흐려집니다.
가운데 칸에 양수를, 둘레 칸에 음수를 주면 반대 일이 일어납니다. 가운데 픽셀과 둘레의 차이가 커집니다. 흐릿한 테두리를 또렷하게 세우는 샤프닝이 이런 커널을 씁니다. 칸 값을 모두 더하면 1 이 되게 짜서 원래 밝기는 그대로 남깁니다.
에지 검출은 밝기가 갑자기 바뀌는 테두리를 찾아내는 일입니다. 샤프닝과 같은 꼴의 커널을 쓰되, 칸 값을 모두 더하면 0 이 되게 짭니다. 그러면 원래 밝기는 빠지고 둘레와의 차이만 남습니다. 고른 바탕은 0 으로 지워지고 테두리에서만 값이 남습니다.
커널을 사진 위에서 한 칸씩 밀며 칸마다 곱해 더하는 이 계산을 합성곱이라고 부릅니다. 영어로는 컨볼루션(convolution)입니다. 한 장을 처리하는 순서는 아래와 같습니다.
flowchart TD
A["원본 사진에서 픽셀 하나 고르기"] --> B["그 픽셀과 둘레 여덟 픽셀 읽기"]
B --> C["아홉 값에 커널의 비율을 곱해 더하기"]
C --> D["결과를 새 사진의 같은 위치에 쓰기"]
D -->|남은 픽셀이 있으면| A
D -->|다 돌았으면| E["새 사진 완성"]
결과를 원본에 덮어쓰지 않고 새 사진에 쓰는 데는 까닭이 있습니다. 덮어쓰면 다음 픽셀을 셈할 때 이미 바뀐 이웃 값을 읽게 됩니다. 그러면 어느 픽셀부터 셈했느냐에 따라 결과가 달라집니다.
곱해 더하지 않는 필터도 있습니다. 미디언 필터는 아홉 값을 크기순으로 늘어놓고 한가운데 값을 고릅니다. 앞의 귀퉁이에서는 100 이 여덟 개라 한가운데 값이 100 입니다. 튄 픽셀이 평균 필터처럼 117 로 남지 않고 둘레와 같은 100 이 됩니다.
위치를 옮기는 기하 변환
기하 변환은 픽셀 값은 두고 위치를 옮깁니다. 사진을 줄이는 일, 늘리는 일, 돌리는 일, 일부만 잘라 내는 일이 여기 듭니다. 새 사진의 픽셀마다 「원본의 어느 위치에서 값을 가져올까」를 셈하는 식으로 돕니다.
문제는 셈한 위치가 원본 픽셀 사이에 떨어질 때입니다. 사진을 1.5배로 늘리면 새 픽셀 대부분이 원본 픽셀 두셋 사이를 가리킵니다. 그 사이의 값을 둘레 픽셀로 짐작해 메우는 일을 보간이라고 부릅니다.
가장 단순한 보간은 가장 가까운 원본 픽셀 값을 가져옵니다. 이를 최근접 이웃 보간이라고 합니다. 계산은 빠릅니다. 대신 늘린 사진이 계단처럼 각져 보입니다.
쌍선형 보간은 둘레 네 픽셀을 거리에 따라 섞습니다. 가까운 픽셀의 값이 더 많이 섞입니다. 늘린 사진이 더 매끄러워지는 대신 계산이 늘어납니다.
줄일 때는 반대 문제가 생깁니다. 가로와 세로를 각각 4분의 1로 줄이면 원본 픽셀 열여섯 개가 새 픽셀 하나로 모입니다. 그중 하나만 골라 가져오면 나머지 열다섯 개의 정보가 버려집니다.
버려진 정보는 엉뚱한 무늬로 돌아옵니다. 가는 줄무늬 옷을 찍은 사진이라면 줄인 사진에서 줄무늬가 끊기거나 원래 없던 물결무늬가 생깁니다. 줄일 때 생기는 이런 무늬를 에일리어싱이라고 부릅니다.
그래서 사진을 줄일 때는 먼저 블러로 잘게 바뀌는 무늬를 흐립니다. 블러를 거친 픽셀에는 둘레 픽셀의 값이 이미 섞여 있습니다. 그다음 픽셀 하나를 골라 가져오면 버려질 둘레 픽셀의 몫도 그 안에 담겨 옵니다.
모일 열여섯 픽셀의 평균을 내서 가져와도 비슷한 효과가 납니다. 평균도 버려질 값을 새 픽셀 하나에 섞어 넣기 때문입니다.
상품 사진을 목록용으로 줄이는 일이 이 경우입니다. 흐리는 단계를 빼먹으면 작은 사진에서 옷감 무늬가 지저분하게 일그러집니다.
컴퓨터 비전 · 그래픽스와 갈리는 선
이미지 처리는 이웃한 두 분야와 무엇을 받고 무엇을 내놓는지로 갈립니다. 셋을 한 표에 놓습니다.
경계에 선 일은 결과의 꼴로 판정합니다. 흐린 상품 사진을 또렷하게 고쳐 내놓으면 이미지 처리입니다. 「이 사진에 신발이 있다」라는 답을 내놓으면 컴퓨터 비전입니다.
두 분야는 서로 이어집니다. 컴퓨터 비전은 사진을 판단하기 전에 크기를 맞추고 잡음을 지우는 전처리를 거칩니다. 그 전처리가 이미지 처리의 계산입니다.
서버에서 사진을 처리할 때 정할 것
백엔드 개발자는 이 계산을 직접 짜기보다 라이브러리로 부르는 일이 많습니다. 사용자가 올린 사진을 줄여 저장하는 일이 대표입니다. 이때 어떤 필터를 쓸지보다 먼저 정할 것이 셋 있습니다.
첫째는 메모리입니다. 사진 파일은 압축되어 있어 작습니다. 처리하려면 압축을 풀어 픽셀 격자로 되돌려야 합니다. 풀린 크기는 가로 픽셀 수와 세로 픽셀 수와 채널 수를 곱한 바이트입니다.
가로 4000, 세로 3000 픽셀인 컬러 사진으로 셈해 봅니다.
const w = 4000, h = 3000;
w * h; // 12000000 · 픽셀 수
w * h * 3; // 36000000 · RGB 바이트
풀린 사진 한 장이 약 36MB(메가바이트)입니다. 파일로 받을 때는 이보다 훨씬 작았던 사진입니다. 이런 사진 여럿을 한꺼번에 풀면 서버 메모리가 금세 찹니다.
파일 크기는 작지만 가로세로를 아주 크게 적어 둔 사진도 있습니다. 풀기 전에는 작아 보여서 받아들이기 쉽습니다. 이렇게 풀리는 순간 메모리를 삼키는 파일을 압축 폭탄이라고 부릅니다.
그래서 풀기 전에 파일 머리에 적힌 가로세로부터 읽습니다. 상한을 넘으면 풀지 않고 거절합니다.
둘째는 처리를 두는 곳입니다. 픽셀마다 계산하니 큰 사진일수록 오래 걸립니다. 업로드 요청 안에서 처리하면 사용자가 그동안 기다립니다.
그래서 요청은 원본만 저장한 뒤 바로 끝내는 일이 흔합니다. 줄이는 일은 큐에 넣어 둡니다. 따로 도는 워커가 큐에서 꺼내 처리합니다.
셋째는 원본을 지키는 일입니다. 줄인 사진에서 원래 크기의 사진을 되살릴 수는 없습니다. 버린 픽셀의 정보가 남아 있지 않기 때문입니다. 그래서 원본을 따로 둡니다. 결과는 언제든 원본에서 다시 만듭니다.
결과를 담는 파일 형식도 같은 문제를 안습니다. JPEG(Joint Photographic Experts Group)은 사람 눈에 덜 띄는 정보를 버려 파일을 줄이는 손실 압축 형식입니다. 고친 사진을 JPEG 으로 다시 저장할 때마다 정보가 조금씩 더 사라집니다. 원본에서 한 번에 결과를 만드는 까닭이 여기에도 있습니다.
관련 항목
이미지 처리의 입력을 이루는 데이터
픽셀 · 이미지 · RGB · 채널 · 색 공간 · 해상도 · 비트 깊이 · 히스토그램
픽셀 하나만 보고 값을 바꾸는 점 연산
점 연산 · 명암 대비 · 감마 보정 · 이진화 · 임계값 · 클램핑 · 히스토그램 평활화 · 그레이스케일
둘레 픽셀을 함께 보는 필터 연산
이웃 연산 · 이미지 필터 · 합성곱 커널 · 합성곱 · 블러 · 가우시안 블러 · 미디언 필터 · 샤프닝 · 에지 검출 · 모폴로지 연산
픽셀의 위치를 옮기는 기하 변환
기하 변환 · 보간 · 최근접 이웃 보간 · 쌍선형 보간 · 리샘플링 · 에일리어싱 · 안티에일리어싱 · 아핀 변환
이미지 처리가 빌려 쓰는 신호 처리 이론
신호 처리 · 푸리에 변환 · 주파수 영역 · 샘플링 · 양자화 · 잡음
처리한 사진을 담는 파일 포맷과 압축
JPEG · PNG · WebP · 압축 · 손실 압축 · 무손실 압축 · EXIF
서버에서 사진을 처리할 때 드는 자원과 장치
메모리 · 작업 큐 · 워커 · GPU · CDN · 압축 폭탄 · 썸네일
이미지 처리를 구현한 라이브러리
OpenCV · Pillow · libvips · ImageMagick · scikit-image
이미지 처리와 입력이나 출력이 겹치는 이웃 분야
다른 이름: image processing · digital image processing · 디지털 이미지 처리 · 영상 처리 · 영상처리