1P by GN⁺ | ★ favorite | 댓글 2개
  • RGB 정규화에서 낯선 이미지 파일을 처리해 다시 8비트로 저장하는 일반 상황이라면 255로 나누는 표준 방식이 적합함
  • 255 방식은 0을 0.0, 255를 1.0으로 매핑해 검은색과 흰색을 직접 다루기 쉽고, GPU의 UNORM-to-float 변환 방식과도 맞음
  • 256 방식은 (img + 0.5) / 256.0으로 각 값을 구간 중앙에 놓아 디더링 같은 작업에서 경계 처리를 단순하게 만들 수 있지만, 0이 0.0이 아니어서 처리 로직이 8비트 입력에 묶임
  • 255 방식은 양 끝 구간이 절반 폭이라 균일한 [0, 1] 난수를 다시 8비트로 반올림하면 0과 255가 다른 값보다 절반 빈도로 나오지만, 실제 이미지 왕복 변환은 손실 없이 동작함
  • 256 방식은 이론상 평균 절대 오차가 1 / 1024로 255 방식의 1 / 1020보다 작지만, 이미 255 방식으로 양자화된 이미지를 잘못된 스케일로 읽으면 오히려 오차를 더함

문제 설정

이미지 처리 프로그램은 8비트 이미지를 부동소수점으로 바꾸고, 처리를 수행한 뒤 다시 8비트 색상으로 저장함

두 변환 방식은 다음과 같음

# 표준: 255로 나누기
pixels = img / 255.0
result = process(pixels)
output = np.trunc(result * 255 + 0.5)


# 대안: 0.5를 더하고 256으로 나누기
pixels = (img + 0.5) / 256.0
result = process(pixels)
output = np.trunc(result * 256)

두 방식 모두 최종 변환 전에 값을 0~255로 제한함

output_8bit = output.clip(0, 255).astype(np.uint8)

표준 방식은 정수 0을 0.0, 255를 1.0에 매핑하며 GPU의 UNORM-to-float 변환 방식과 같음

대안 방식은 0을 0.5 / 256 = 0.001953125에 매핑하므로, 검은 픽셀을 감지하려면 이 상수를 알아야 함

255로 나누는 표준 방식의 특성

표준 방식은 [0, 1] 범위 안에서 양 끝 값의 구간이 다른 구간보다 사실상 절반 폭이 됨

균일한 [0, 1] 난수를 만들고 trunc(result * 255 + 0.5)로 반올림하면 0과 255는 다른 정수보다 절반 빈도로 나타남

하지만 원래 8비트 이미지는 uint8 → float → uint8 왕복 변환에서 손실 없이 돌아옴

또한 처리 결과가 0.0이나 1.0을 약간 벗어나도 클램프와 반올림으로 올바른 정수 구간에 들어갈 수 있음

예를 들어 부동소수점 색상에서 0.005를 빼면 표준 방식의 검정은 음수가 되지만, 최종 결과는 여전히 정수 0이 됨

trunc(255 * (-0.005) + 0.5) = 0

부동소수점 정확성과 구간 중앙 배치

255 방식의 값은 일부가 정확히 표현되지 않음

예를 들어 128 / 255.0 ≈ 0.501961이지만 128 / 256.0 = 0.5

이 차이는 32비트 부동소수점의 23비트 가수에서 최하위 비트 수준의 반올림 오차이며, 크기는 2^-23보다 작음

따라서 이 부정확성은 실제 기술적 문제라기보다 미적인 문제에 가까움

256 방식은 각 부동소수점 값을 두 정수 사이의 정확한 중앙에 놓음

이 성질은 원래 양자화된 값이 정확히 무엇이었는지 모를 때 두 연속 정수 사이의 평균점을 쓰는 절충으로 볼 수 있음

Andrew Kesler의 2015년 글 “Converting Color Depth”는 이 방식이 디더링에서 노이즈를 더할 때 경계 처리를 덜 신경 쓰게 만든다고 봄

반대로 표준 방식의 양끝 구간은 노이즈 분포를 일관되게 유지하려면 주의 깊은 처리가 필요함

양자화 관점

두 방식은 균일 스칼라 양자화기(uniform scalar quantizer)로 볼 수 있음

Wikipedia의 quantization 설명)은 signed input data의 균일 양자화기를 주로 mid-riser와 mid-tread로 나눔

mid-tread는 0값 재구성 레벨을 가지며, mid-riser는 0값 분류 임계값을 가짐

공식은 다음처럼 대응됨

방식 인코딩 디코딩
mid-tread k = trunc(x L + 0.5) y_k = k / L
mid-riser k = trunc(x L) y_k = (k + 0.5) / L

표준 방식은 L=255를 쓰는 mid-tread 형태이고, 대안 방식은 L=256을 쓰는 mid-riser 형태임

표준 방식은 0.0과 1.0에 양끝을 맞추는 프로그래밍 편의를 얻는 대신, 8비트 입력에 최적인 구간 배치와는 다름

재구성 오차와 실제 이미지 처리

균일 분포의 실수 x ∈ [0, 1]를 8비트 정수로 인코딩하고 다시 실수로 재구성하는 시스템을 직접 설계한다면 256 방식이 이론상 더 정밀함

표준 방식의 표현 가능 범위는 [-0.5 / 255, 255.5 / 255]가 되어 [0, 1]에 꼭 필요한 것보다 구간 간격이 넓어짐

StackOverflow 사용자 Peter Mudrievskij의 계산에 따르면 평균 절대 오차는 255 나누기에서 1 / 1020, 256 나누기에서 1 / 1024

하지만 이미 저장된 8비트 RGB 이미지를 읽어 처리하는 상황에서는 저장 당시 잃어버린 정보가 복원되지 않음

이미지가 255를 곱하고 반올림하는 방식으로 양자화됐다면, 로드할 때 256으로 나누어도 정밀도가 돌아오지 않음

다른 사람이 만든 이미지는 대부분 표준 방식으로 양자화됐을 가능성이 높으므로, 대안 공식으로 읽으면 이론적으로 잘못된 스케일 팩터를 쓰게 됨

실제로는 색상이 절대 측정값처럼 동작하지 않아, 약간 더 작은 범위와 작은 오프셋에서 처리하는 결과가 됨

두 양자화기의 인코딩 단계와 디코딩 단계를 섞으면 깨진 코드가 됨

결론

낯선 사람이 제공한 이미지를 처리한다면 RGB 값은 255로 정규화해야 함

부동소수점 값이 정확하지 않다는 이유나 추상적인 재구성 오차가 더 크다는 느낌만으로 256 방식을 선택할 근거는 약함

이미지 저장과 로딩을 모두 제어하고, 0이 0에 매핑될 필요가 없으며, 처리 코드가 8비트 동적 범위에 묶여도 괜찮다면 256으로 나누어 약간 더 높은 이론적 정밀도를 노릴 수 있음

댓글과 토론

Hacker News 의견들
  • 색상 값이 정확히 무엇을 뜻하느냐는 성분당 8비트에서는 대체로 별일 아님. 분모가 255냐 256이냐 차이로 생기는 오차가 아주 작고, 차이를 보려면 색 감각이 좋고 화면에 아주 가까이 가야 하며, 모니터나 휴대폰 화면도 보통 보정되어 있지 않기 때문임
    하지만 마이크로컨트롤러로 VGA 신호를 만들고 색 출력 핀이 8개(빨강 3, 초록 3, 파랑 2)뿐이면 꽤 골치 아파짐. 이때 색상 값은 VGA 모니터에 보내야 하는 0V~0.7V 전압 레벨 그 자체임
    파랑 채널은 0→0V, 1→0.23V, 2→0.47V, 3→0.7V로 매핑되고, 빨강/초록은 0→0V, 1→0.1V, …, 7→0.7V로 매핑됨. 양 끝을 빼면 파랑 전압이 빨강/초록 전압과 하나도 맞지 않아서 순수한 회색을 볼 수 없고, 가장 가까운 색도 차이 방향에 따라 파란색이나 노란색 기가 약간 섞임
    게다가 파랑을 다른 채널과 섞는 거의 모든 그라데이션도 어긋나 보임. 예를 들어 순수 빨강에서 순수 흰색으로 가는 선상의 가장 가까운 색들이 조금 주황색이나 보라색처럼 보임
    Raspberry Pi Pico 2에서 이중 버퍼 320x240 프레임버퍼로 8비트 색 VGA 출력을 하는 코드는 여기 있음: https://github.com/moefh/pico-vga-8bit-demo

    • 어릴 때 노이즈 낀 CRT 화면을 보면서 가장자리의 희미한 파란색과 노란색 선을 봤던 기억이 남. 왜 하필 그 두 색인지 늘 궁금했는데, 같은 원인이라면 이제야 알게 된 셈임
    • 감마 보정을 빼먹었음. 값을 0~255 범위에서 전압으로 바꾸기 전에 PC는 보통 그 값을 2.2제곱으로 올림
      이러면 작은 값과 큰 값의 차이가 훨씬 더 두드러짐: 2^2.2 = 4.595, 255^2.2 = 196,964.699
    • 이 문제에는 시간 방향 디더링이 가장 좋아 보임. 픽셀별 델타-시그마 변조는 꽤 쉽게 할 수 있음
      30Hz로 바뀐다면 사람이 약간 파란색과 약간 노란색의 차이를 구분하기는 어려울 듯함
    • 그래서 80년대에 RGBI 색상이 그렇게 흔했나 봄
  • 255를 지지하는 논거로, 흑백 이미지라는 극단적 사례를 보면 됨. 단일 비트에서 0은 검정, 1은 흰색임
    0은 0.0에, 1은 1.0에 매핑되어야 한다는 건 꽤 명확함. 흑백이지 밝은 회색(0.25)과 어두운 회색(0.75)이 아니기 때문임. 즉 흑백 이미지는 2가 아니라 1로 정규화함
    2비트라면 보통 0=검정, 1=밝은 회색, 2=어두운 회색, 3=흰색이므로 0.0, 0.33, 0.66, 1.0으로 매핑하는 게 자연스러움. 검정은 검정이고 흰색은 흰색이어야 하며 간격도 같아야 하니 3으로 정규화함
    이 논리를 8비트까지 이어가면 255로 정규화하게 됨. 8비트에서는 차이가 아주 작아지더라도 검정은 0.0, 흰색은 1.0이어야 하기 때문임
    다른 방식인 8비트에서 256 정규화를 쓰면 출력 범위가 비트 수에 따라 달라짐. 1비트는 [0.25, 0.75], 2비트는 [0.125, 0.875]처럼 됨. 보통 원하는 건 비트 수가 늘수록 뉘앙스가 늘어나는 것이지, 대비가 달라지는 게 아님

  • 정말 생각할 거리를 주는 글이었고, 개인적으로 갖고 있던 전제를 다시 따져보게 됨
    전기공학 배경에서 보면 글의 “두 종류의 양자화기” 제시는 동의하기 어려움. 수학적으로는 엄밀하지만 실제 시스템에 기반한 설명은 아님
    ADC에는 항상 본질적으로 ±1/2 LSB 양자화 불확실성이 있음. 전달 특성은 항상 mid-tread 샘플링이고, 적어도 반례를 본 적은 없음. 이는 양극성 ADC든 단극성 ADC든 마찬가지임
    최저 코드는 음전압 기준이고 최고 코드는 양전압 기준임. 전달 특성 그래프는 글에서 보인 것처럼 최고/최저 구간이 사실상 1/2 LSB 폭이라는 점을 보여줌
    단극성 시스템에서는 중간 전압을 정확히 표현할 수 없고, 다시 말해 회색 문제가 생김. 양극성 시스템에서는 0V가 mid-tread의 N/2 값이지만, 그렇다고 “256개 구간”이 있다는 뜻은 아님
    그래서 (VREF+ - VREF-) * k / (2^N - 1)을 계속 쓸 것임. 즉 255 정규화에 동의함. 결국 울타리 기둥 오류와 같아서, 값은 N개지만 구간은 N-1개임. 값보다 구간이 적다면 구간 하나를 두 값 사이에 나눠야 하고, 그래서 끝점에 1/2 LSB 구간이 생김

    • 내가 본 모든 ADC 문서는 양의 풀스케일을 표현할 수 없다고 적고 있음. 예를 들어 8비트 ±1V ADC에서 -128은 -1V를 뜻하고, +127은 127/128=0.99219V를 뜻함
      126에서 127로 넘어가는 전이는 양의 전체 범위에서 1.5 LSB 떨어진 지점에서 일어남. 1 LSB 차이는 2/255=0.00784V가 아니라 1/128=0.00781V 차이를 의미함
      하지만 실제로 전압과 불확실성이 중요하다면 이런 차이는 대부분 별 의미가 없음. 기준 전압에 바이어스가 있고 선형성 오류도 있음. 1 LSB는 1/128에도 2/255에도 정확히 맞지 않으며, 보정을 위한 매개변수가 필요해짐
  • 이건 과학 계산에서 말하는 노드 중심 샘플과 셀 중심 샘플의 차이를 1차원으로 본 것과 비슷함. 값이 구간의 가운데(또는 삼각형/사면체의 가운데)에 있는지, 구간 경계(또는 삼각형/사면체의 꼭짓점)에 있는지를 정해야 함
    과학 계산에서는 값을 어떻게 해석해야 하는지 모른 채 데이터 처리를 시작하는 건 말이 안 됨. 오디오 신호 처리에서도 정수 스트림만 받았다면 그 정수가 어떤 표현 의도를 갖는지, 예를 들어 mu-law 인코딩인지 선형인지 알아야 원 신호에 대해 계산할 수 있음. 값에 붙은 메타데이터가 그 답을 제공해주길 기대하게 됨
    하지만 8비트 픽셀 값에서는 표현 의도를 전달할 수 있는 제대로 된 파일 형식의 메타데이터가 없다면 표류하게 되고, 정답은 없음. 글쓴이가 말하듯 자기 용도에서 더 좋은 결과를 내는 쪽을 고른다고 해서 비난할 수는 없지만, 맥락 없는 비트는 의미가 훼손된다는 점은 알릴 수 있음

    • ESA의 Sentinel-2 level-2 위성 이미지 양자화에서 쓰는 정규화 값이 떠오름
      대략 이런 식임: Digital Number DN=0은 “NO_DATA” 값으로 남고, DN이 [1; 1;215-1] 범위일 때 L2A SR 반사율 값은 L2A_SRi = (L2A_DNi + BOA_ADD_OFFSETi) / QUANTIFICATION_VALUE가 됨
      https://sentiwiki.copernicus.eu/web/s2-products
  • 여기에는 0부터 255까지 256단계가 있다고 가정하는 오류가 있음. 사실 8비트로 표현할 수 있는 값이 256개이고, 0(검정)에서 255(순수 흰색)까지의 간격은 255개임
    그래서 255로 나누는 건 문제가 아님. 물론 128이 정확한 반 회색은 아니고, 0~255의 양자화된 8비트 값은 거의 항상 선형 지각 공간이 아니라 sRGB에 있음
    현대 API에서 샘플링 위치를 다룰 때도 비슷한 혼동이 생김. 위치가 픽셀 중심이 아니라 좌표로 지정되기 때문임

    • BeOS API는 픽셀 중심 기준임. 이제 아무도 신경 쓰지 않겠지만
  • 대수적으로 보면 답은 명확히 f(x) -> [0, 255]임
    f(n * 0) == n * f(0)이 성립하지 않으면 이상한 일이 벌어짐. 예를 들어 f(x) -> [0, 255]라면 f(0) + f(0) + f(0) = 0 + 0 + 0 = 0 = f(0)임
    반면 f(x) -> [0.5/8, 7.5/8]라면 f(0) + f(0) + f(0) = 0.5/8 + 0.5/8 + 0.5/8 = 1.5/8 != f(0)이 됨
    뒤쪽을 택하면 x 쪽에서 한 계산과 f(x) 쪽에서 한 계산이 서로 맞아떨어지리라고 기대할 수 없음. 즉 대수적 대응이 깨짐

  • +0.5 해법을 지지하고 싶음. 첫째로 가장자리의 반쪽 크기 구간이 마음에 들지 않고, 둘째로 255 기반 표현은 보통 HDR이 아니라 SDR 이미지
    RGB 값은 어떤 적응 상태에 대한 휘도를 나타내며, 낮 장면의 “0”은 “휘도 0”이 아님. 가장 밝은 지점의 약 0.001배 정도일 뿐이고, 광자는 수백만 개라 0보다 훨씬 많음
    어떤 의미에서 눈은 대비를 미끄러지는 척도로 경험하며, 시스템 안에 절대적인 0은 없음. 예를 들어 방송 시스템은 역사적으로 SDR 휘도 범위로 16~235를 썼음. “반드시 0이 있어야 한다”는 논리는 편향이 생긴다고 보고, 대부분의 경우 0은 필요하지 않다고 생각함

    • VFX용 이미지 처리와 렌더링을 많이 해본 입장에서, 이 뒤에 색공간 변환이 일어난다는 점을 잊은 것 같음. 예전 SDR에서는 sRGB의 선형 Rec.709로, 최신 형식에서는 더 넓은 색역으로 변환하는 식임. 따라서 동적 범위가 눌리는 일은 로드 이후에 일어남
      또한 이미지 처리와 합성 워크플로 상당수는 옳든 그르든 0이 0을 뜻한다고 가정함. 그래서 8비트에서 0u는 0.0f로, 255는 1.0f로 매핑된다고 보는 경우가 많음. 마스크나 알파에서 0 값이 0.0을 조금 넘게 되면, 어딘가의 코드가 0.0 하드 임계값으로 다른 연산을 마스킹하면서 아티팩트가 생김. 반대로 알파에서 255가 더 이상 1.0f가 아니면 사전 곱셈 후 객체가 아주 약간 투명해짐
      같은 일이 +0.5 때문에 마스킹에서 254가 1.0f가 될 때도 생길 수 있음
    • 글은 RGB에 초점을 맞추지만, 같은 양자화 문제는 이산 표현과 연속 표현 사이에 매핑되는 모든 종류의 신호에 존재함
      핵심은 광자 0개를 표현하느냐가 아니라, 1바이트에 저장되는 정보를 최대화하느냐임. 이상적으로는 바이트 값 0을 덜 쓰면 안 되고, 0번째 버킷에 들어갔어야 할 데이터에 바이어스를 더해서도 안 됨. 밝음에서 매우 밝음으로 가는 색공간이라도, 모든 바이트가 밝기 범위의 같은 크기 조각을 나타내게 해야 함
    • 역사적으로 방송 시스템이 SDR 휘도 범위로 16~235를 썼다는 게 문제임. 안타깝게도 “현대적” HDMI도 아직 이 이상한 관행에 시달려서, 디스플레이와 소스가 합의하지 않으면 화면이 씻긴 듯 보이거나 검은색이 뭉개짐
    • 두 해법 모두 0.5를 더함. 차이는 그 일이 과정의 어디에서 일어나느냐임
    • 흥미로운 아이디어지만 세상이 흔들리는 느낌임. 처리 프로그램 입장에서는 예전의 검정(0.0)과 흰색(1.0)이 아주 어두운 회색과 아주 밝은 회색이 되어버림
  • 자가 12인치까지 있다면, 자 위의 점 개수인 13이 아니라 길이 L로 정규화해야 함

    • 그 비유가 헷갈림. “자”가 0~255로 표시된 256개의 점을 가진 255인치 자인지, 아니면 1인치 구간 256개를 가진 256인치 자라서 L = 256×1인지 모르겠음
    • 실제로 세려는 게 울타리 기둥이라면 울타리 기둥 오류는 오류가 아님
    • 맞지만 >> 8이 훨씬 빠름
    • 숫자가 점을 나타낸다고 누가 정했나? 점 사이의 구간을 나타내는 걸 수도 있음
    • 내가 멍청한 건가. 0은 시작점에서 시작하지 않나?
  • 한동안 생각하지 않았던 주제를 다뤄서 읽기 즐거운 글이었음. 게임 개발에서 게임 로직은 부동소수점 수학을 쓰는데 픽셀 아트는 정수 좌표에 그려야 했던 순간들이 떠올랐음
    몇 군데에서 +0.5와 비슷한 방식을 써서 덜 이상해 보이게 하려 했음. 특히 움직이는 카메라가 있을 때 그랬고, 카메라도 잘라내야 했음
    아래에 링크된 Jonathan Blow의 2002년 글 [1]도 재미있었음. 첫 번째 글의 시각화가 더 깊이 들어갈 때 많은 도움이 됨
    [1] https://web.archive.org/web/20240706043551/https://number-no...

Lobste.rs 의견들
  • 지저분해 보이지만 맞음, 값은 255가 맞음
    직관적이지 않다면 2비트 퇴화 사례로 보면 됨. 가능한 정수 값이 0, 1, 2, 3뿐일 때 정수→부동소수점 변환을 전부 계산해 보면, 검정/흰색이 검정/흰색이 아니거나 간격이 명백히 고르지 않은 이상한 동작을 피하려면 0.0, 0.33..., 0.66..., 1.0이 됨
    따라서 역변환은 4(2^2)가 아니라 3을 곱하는 방식이 됨
    • 앞부분은 맞지만, 거기서 “역변환은 3을 곱해야지 4가 아니다”가 따라오지는 않음
      역변환에는 양자화(반올림) 가 필요하고, 바로 그 지점이 대칭성을 깨는 핵심임
      0..=1 범위의 균등한 실수 그라디언트를 만들어 0, 1, 2, 3으로 양자화해 보면, 3을 곱하면 결과가 고르지 않다는 걸 알 수 있음. ×3 후 round()는 1과 2가 과대표현되고, ×3 후 floorceil은 0이나 3을 특이점처럼 접어 넣어서 그라디언트가 4색 중 3색만 쓰는 것처럼 보이게 만듦
      /3×3 논리는 정확한 숫자를 왕복 변환할 때는 괜찮아 보이지만, 중간값은 반올림 선택에 크게 영향을 받고 데이터 처리를 시작하는 순간 중요해짐
      정수 비율이 균등해지는 건 (4-ε)를 곱하고 내림할 때뿐이며, 이는 ×4, floor(), clamp()와 같음. 이상한 1 차이 또는 ε 차이 오류처럼 느껴지지만, 직관적으로는 가장 보기 좋은 해법임
  • 제목 때문에 많이 헷갈렸음. 일부러 그런 건지 모르겠지만, 결국 “0..1이 [0..255.0] 에 대응하나, [0.5..255.5] 에 대응하나?”처럼 보임
    내게 답은 늘 “당연히” [0.0..255.0]이었지만, 아마 모두에게 당연한 건 아닌 듯함
    글에서는 “극단” 구간이 다른 구간의 절반 용량만 가진다고 하는데, 이 프레이밍도 맞지 않다고 봄
    [0..1] 밖의 값이 존재하지 않는다면 좁은 구간처럼 보이는 건 렌더링 산물임. 범위를 벗어난 값이 없다는 지식을 가지고 버킷을 잘라냈기 때문에 더 좁게 렌더링된 것뿐임
    반대로 [0..1] 밖의 값이 존재한다면 그 범위는 무한함. 글은 후자는 인정하지만 전자는 인정하지 않음
    첫 번째를 인정하는 순간 올바른 동작은 분명해 보이지만, 이런 글이 나왔다는 사실 자체가 객관적으로 “분명한” 문제는 아니라는 뜻이기도 함 :D
    • 정말로 0…255.0이 당연하다면, 어떤 부동소수점 값 범위가 정수 0으로 돌아가고 어떤 값이 정수 255로 돌아가야 함?
      0..<1이 정수 0으로 가고, 254>..255.0이 정수 255로 간다고 하면 128이 잡아먹힘. 아마 127.5..128.5가 128로 가길 원할 텐데, 그러면 이 절반들은 어디로 가야 함?
      128을 맞추려고 전체를 조금 이동시키면 0..0.99609375가 정수 0으로 매핑됨
  • 표준 접근도 사람들이 자연스럽게 round()를 호출하면서 생긴 것처럼 보임
    사람들에게 그 방식이 꽤 자연스럽게 느껴지니, 단순함 때문에 표준이 된 것 같음
  • 256으로 달성하려던 것의 반대 방식도 쓸모가 있을지 궁금함. 즉 0.0은 0, 1.0은 255로 보내고, 나머지 부동소수점 값은 1부터 254로 매핑하는 방식임
    uint8_t output = 0.0f >= result  
                     ? 0  
                     : 1.0f <= result  
                     ? 255  
                     : 1 + 253*result;  
    
    처리 중에도 검정은 검정으로, 흰색은 흰색으로 유지되면 좋겠음
    • 이렇게 하면 0과 255가 단위 구간에서 다른 숫자보다 더 큰 몫을 가져감. 대략 0.8%, 즉 255/253 정도임
  • 첫 번째 이미지가 내 환경에서는 깨져 보임
    • 글 작성자임. 이미지 파일이 손상됐다는 뜻인가요? pngcrush로 압축하긴 했음. 아니면 이미지 내용이 뭔가 잘못됐다는 뜻인가요?