QR 코드 로고는 얼마나 클 수 있을까? 측정했어요
“오류 정정 30%”는 로고 허용 크기가 아니에요. 모든 오류 정정 수준에서 구멍을 키워 실제 디코더가 실패하는 지점을 측정했어요.
이 글의 새로운 점: 오류 정정 수준별로 실제 기호에 점점 커지는 구멍을 뚫고 jsQR로 디코드해 측정한, QR 코드가 견디는 가장 큰 중앙 패치예요. L에서 4%, M에서 7%, Q에서 12%, H에서 17%예요. 또한 아무도 공개하지 않는 두 결과도 담았어요. 흩어진 손상은 4%에서 코드를 깨뜨리는 반면 한 덩어리로 된 손상은 17%까지 버티고, 파인더 패턴 위의 같은 패치는 1%에서 깨뜨려요.
QR 코드에 로고를 넣는 방법을 다룬 가이드는 거의 모두 같은 두 가지를 말해요. 오류 정정 레벨 H를 쓰라는 것과, 코드의 30%까지 복구할 수 있다는 것을 기억하라는 것이에요. 뒤의 내용은 너무 자주 반복되어서 하나의 디자인 지침이 되어 버렸어요. 바로 최대 30%까지 가려도 된다는 말이에요. 하지만 그 숫자가 실제로 뜻하는 바는 이것과 달라요.
그래서 이 숫자가 실제로 무엇을 뜻하는지 직접 측정해 봤어요.
# "30% 오류 정정"이 실제로 뜻하는 것
QR 코드는 데이터를 몇 개 잃어도 버틸 수 있는 픽셀로 저장하지 않아요. 코드워드를 저장하는데, 코드워드는 여덟 개의 모듈로 이루어진 블록이고 Reed–Solomon 오류 정정으로 보호돼요. 레벨 H의 대표 수치는 이 코드워드 중 대략 몇 개를 복원할 수 있는지를 나타내요. 그리고 그 복원 능력의 일부는 여러분이 아무것도 건드리기 전에 이미 인코딩 자체의 오버헤드로 쓰여요.
숫자 자체보다 중요한 결과가 두 가지 있어요.
- 손상은 모듈 단위가 아니라 코드워드 단위로 세요. 모듈 하나를 망가뜨리면 그 모듈이 속한 코드워드 전체가 망가져요.
- 파인더 패턴, 타이밍 패턴, 포맷 정보는 오류 정정으로 전혀 보호되지 않아요. 이것들은 디코더가 심볼을 처음 찾을 때 쓰는 요소이고, 잃어버리면 복원할 방법이 없어요.
그러니 "30%"는 수학에 관한 사실이지, 그림을 얼마나 가려도 된다는 허가증이 아니에요. 허가증은 지금부터 보여 드릴게요.
# 측정된 레벨별 로고 예산
방법은 여러분이 직접 다시 실행해 볼 수 있도록 적어 둘게요. MostlyQR이 제공하는 인코더(buildMatrix, MIT 라이선스의 node-qrcode를 감싼 것)로 https://mqr.sh/AB12CD를 인코딩해요. 모든 오류 정정 레벨에서 심볼 버전을 3(29 모듈)으로 고정해서 EC만 변수가 되도록 해요. 8픽셀 모듈에 모듈 4개짜리 여백(quiet zone)을 포함해서 렌더링해요. 심볼 면적의 1%에서 시작해서 한 번에 1%씩 키워 가며 가운데에 흰색 정사각형을 덮어씌우고, 단계마다 픽셀을 jsQR 1.4.0에 넘겨요. 기록하는 값은 처음으로 디코딩에 실패하기 직전의 마지막 크기예요.
| 오류 정정 | 여전히 디코딩된 가장 큰 가운데 흰색 패치 |
|---|---|
| L | 심볼 면적의 4% |
| M | 7% |
| Q | 12% |
| H | 17% |
레벨 H는 레벨 L의 약 네 배에 해당해요. 실제로 의미 있는 차이이고, 모든 가이드가 H를 쓰라고 말하는 이유이기도 해요. 하지만 30%와는 거리가 한참 멀어요.
이 숫자들을 바탕으로 한 대략적인 기준은 이래요. 레벨 H에서 코드 면적의 약 **15%**를 차지하는 가운데 로고, 즉 코드 너비의 약 39%짜리 정사각형이면 측정된 한계 안에 들어가고 여유도 조금 있어요. 레벨 M의 측정 한계는 7%이고, 이는 너비의 약 26%짜리 정사각형에 해당해요. 같은 방식으로 여유를 두고 줄이면 대략 5분의 1 수준이 돼요.
같은 실험을 픽셀 단위를 16으로 바꿔서 다시 돌려 봤는데 결과가 완전히 같았어요. 이건 예상된 결과예요. 이 한계는 렌더링 방식이 아니라 심볼 자체의 성질이기 때문이에요.
# 흩어진 노이즈보다 덩어리가 저렴한 이유
우리가 예상하지 못한 결과가 하나 있었어요. 이 결과가 바로 가운데 로고가 애초에 왜 잘 작동하는지를 설명해 줘요.
연속된 하나의 패치 대신, 심볼 전체에서 데이터 모듈을 무작위로 하나씩 뒤집었어요. 파인더, 타이밍, 얼라인먼트 패턴은 건드리지 않았고요. 그리고 디코딩이 실패할 때까지 비율을 높였어요. 같은 페이로드, 같은 버전, 같은 디코더로 무작위 시드 다섯 개를 써서 실험했어요.
| 오류 정정 | 최대 분산 손상 (5회 실행의 중앙값) |
|---|---|
| L | 데이터 모듈 567개의 1% |
| M | 3% |
| Q | 3% |
| H | 4% |
(v3 심볼은 모듈이 841개인데, 그중 567개가 데이터를 담아요. 나머지는 파인더, 타이밍, 얼라인먼트, 포맷 패턴이고, 이 실험에서는 건드리지 않았어요.)
같은 레벨에서 한 덩어리로 17%를 망가뜨릴 수 있었던 반면, 흩어뜨리면 4%만으로도 실패했어요.
이유는 코드워드에 있어요. 17%짜리 연속된 정사각형은 소수의 코드워드를 완전히 망가뜨려요. 같은 양의 손상을 고르게 흩뿌리면 많은 코드워드의 일부가 망가져요. 그리고 모듈 하나만 잘못된 코드워드는 모듈 여덟 개가 잘못된 코드워드와 똑같이 망가진 것으로 취급돼요. 오류 정정은 손상의 양이 아니라 피해를 입은 코드워드의 개수를 세거든요.
그래서 흔히 생각하는 것과 반대 결론이 나와요. 한 곳에 있는 깔끔한 하나의 도형이 오류 정정을 쓰는 가장 저렴한 방법이에요. 코드 전체에 얼룩, 질감, 반투명 워터마크, 사진 필터를 입히는 방식은 손상이 적어 보여도 가장 비싸요.
# 절대 가리면 안 되는 세 모서리
같은 방식의 테스트를 커지는 정사각형으로 진행하되, 위치를 다른 두 곳으로 옮겨서 해 봤어요.
| 패치 위치 | 레벨 H에서의 한계 지점 |
|---|---|
| 중앙 | 17% |
| 오른쪽 아래 모서리 (얼라인먼트 패턴 위) | 5% |
| 왼쪽 위 모서리 (파인더 패턴 위) | 1% |
레벨 L에서는 왼쪽 위 경우가 첫 단계에서 바로 실패했어요. 1% 미만이었죠.
이 모서리들은 내용이 아니라 구조예요. 큰 동심 정사각형 세 개가 파인더 패턴이고, 오른쪽 아래 근처에 안쪽으로 들어간 작은 정사각형이 얼라인먼트 패턴이에요. 디코더는 이것들을 이용해 심볼이 어디에 있는지, 얼마나 큰지, 어떻게 회전되었거나 기울어졌는지를 알아내요. 하나라도 가리면 디코딩 단계 자체에 도달할 수 없어요. 오류 정정 레벨은 거의 차이를 만들지 못해요. 오류 정정은 애초에 이 부분을 보호하지 않았기 때문이에요.
그래서 로고를 넣을 가장 안전한 자리가 가운데이고, "그냥 로고를 작게 만들어서 구석에 넣으면 된다"는 생각은 정확히 반대로 가는 거예요.
# 이 테스트가 보여 주지 않는 카메라의 영향
위의 모든 결과는 깨끗하고 인위적인 최선의 조건에서 얻은 것이에요.
- 디코더가 하나예요. jsQR은 좋고 널리 쓰이는 디코더지만, 스마트폰 카메라의 스택은 jsQR이 아니에요. 실제 리더기는 손상된 코드를 얼마나 적극적으로 해석하려고 하는지가 서로 달라요.
- 픽셀이 완벽해요. 흐림, 반사광, 원근 왜곡, 인쇄 오차가 없어요. 이 중 무엇이든 있으면 허용 범위는 줄어들어요.
- 깔끔한 흰색 구멍이에요. 실제 로고에는 색상, 가장자리, 아래 모듈과의 안티앨리어싱이 있어요. 로고 뒤의 흰색 바탕은 장식이 아니에요. 가려진 영역을 모호하지 않게 만들어 주는 역할을 해요.
이 표는 설계할 때 목표로 삼을 값이 아니라, 이상적인 조건에서 측정한 상한선으로 보세요. 우리가 권하는 것은 그 여유분의 약 3분의 2만 쓰는 거예요.
# 직접 다시 실행해 보기
위의 모든 수치는 글에 손으로 적은 것이 아니라 스크립트가 출력한 값이에요. 우리 저장소의 draft/mostlyqr/seo/benchmarks/logo.js에 있어요. 이 스크립트는 각 레벨에서 심볼을 렌더링하고, 패치를 1%씩 키우고, 시드 다섯 개로 분산 손상 비교를 실행하고, 두 번째 픽셀 배율에서 전체를 다시 돌려요. 또한 이 글 끝부분에서 인용한 경계값을 우리 검증기에 대고 확인해 봐요.
# 실제로 해야 할 일
- 로고가 있으면 오류 정정 레벨 H를 쓰세요. 허용 예산이 대략 네 배로 늘어나고, 짧은 링크 기준으로 한 변에 모듈이 약 4개 더 생겨요. 이 부분은 짐작하지 말고 계산할 수 있는 인쇄 크기 기준으로 확인하세요.
- 로고는 가운데에 두고, 코드 면적의 약 15% 아래로 유지하세요. 레벨 H 기준으로 코드 너비의 약 39%짜리 정사각형이에요.
- 단색 바탕 위에 올려서, 가려진 영역이 모호하지 않고 깔끔하도록 하세요.
- 세 개의 모서리 정사각형에 아무것도 닿지 않게 하세요. 로고, 프레임, 둥근 모서리 자르기 모두 해당해요.
- 나쁜 스마트폰으로 실제 인쇄물을 테스트하세요. 여기 나온 모든 수치는 어떤 디자인을 테스트할 가치가 있는지 알려 줄 뿐이에요. 실제로 작동하는지는 테스트만이 알려 줘요.
이미지 자체가 로고 하나가 아니라 코드 전체가 되기를 원하신다면, 그건 예산이 다른 별개의 기법이에요. 우리는 사진 QR 코드를 따로 측정했는데, 이 방식은 오류 정정을 전혀 쓰지 않는다는 것을 확인했어요. 그리고 그냥 로고가 들어간 코드가 필요하다면, MostlyQR 생성기는 로고를 추가하는 순간 오류 정정을 자동으로 레벨 H까지 올리고, 모듈 4개짜리 여백을 유지하며, 로고 크기 기본값을 코드 너비의 22%로 설정해요. 면적으로는 4.8%이고, 위에서 측정한 모든 범위 안에 여유 있게 들어가요.
우리 코드에 대해 한 가지 솔직하게 짚을게요. 수치를 공개하는 만큼 말씀드리자면, 너무 큰 로고를 거부하는 가드는 명목상의 30% 복구 수치에 15% 할인을 적용해서 계산해요. 그래서 레벨 H에서 면적의 약 25%를 덮는 로고까지 통과시켜요. 우리 측정으로는 실제 한계는 17% 근처예요. 기본값을 바꾸지 않는 한 기본값은 충분히 안전하지만, 이 제한은 근거가 뒷받침하는 것보다 느슨해요. 그리고 이건 QR 코드의 미묘한 특성 문제가 아니라 우리 검증기의 버그예요. 수정 목록에 올라가 있어요.
자주 묻는 질문
로고가 QR 코드의 30%를 덮어도 될까요?
아니요. 오류 정정 수준 H의 “30%”는 Reed-Solomon이 복구할 수 있는 코드워드, 즉 8모듈 블록을 가리키는데, 그 용량의 일부는 인코딩 자체에 이미 쓰여 있어서 여러분을 위해 남아 있지 않아요. 우리 테스트에서 H 코드를 흰색으로 가린 뒤에도 디코드가 된 가장 큰 중앙 정사각형은 기호 면적의 17%였어요. L에서는 4%였고요. 30%는 수학에 대한 설명으로 보고, 디자인 허용치로 보지 마세요.
로고가 있는 QR 코드에는 어떤 오류 정정 수준을 써야 하나요?
크기를 감당할 수 있다면 H요. 우리 측정에서 H는 L에 비해 허용되는 중앙 패치를 약 네 배로 늘렸어요(17% 대 4%). 대신 짧은 링크 기준으로 한 변에 모듈 4개가 더 늘어나요. 한 가지 주의할 점은, 오류 정정과 작은 인쇄 크기가 같은 예산을 두고 경쟁한다는 거예요. 로고가 있는 H 코드는 더 작게가 아니라 더 크게 인쇄해야 해요.
모서리를 가리면 QR 코드가 작동하지 않는 이유는 무엇인가요?
모서리는 데이터가 아니기 때문이에요. 큰 정사각형 세 개는 파인더 패턴이고, 오른쪽 아래의 작은 정사각형은 얼라인먼트 패턴이에요. 둘 다 리더가 디코드하기 전에 기호의 위치를 찾고 정렬하는 데 쓰여요. 오류 정정으로 보호되지 않으므로 이를 복구할 방법이 없어요. 우리 테스트에서 파인더 위의 패치는 기호 면적의 1%에서 디코딩을 깨뜨렸고, 가운데에서는 17%였어요.
QR 코드의 로고에 흰색 바탕이 필요한가요?
실제로는 필요하고, 우리 수치도 그것을 전제로 해요. 우리는 정사각형을 흰색으로 가려서 테스트했는데, 이는 가장 좋은 경우예요. 디코더가 균일하게 밝은 영역으로 취급할 수 있는 깨끗하고 대비가 높고 가장자리가 선명한 구멍이기 때문이에요. 흰 바탕 없이 모듈 위에 로고를 바로 올리면, 가장자리 주변에 애매한 반쯤 어두운 모듈이 생기고, 이것은 같은 면적의 깨끗한 흰색보다 더 큰 손실을 가져와요.