본문 바로가기

카테고리 없음

같은 값으로 채도를 올렸는데, 어떤 색상은 왜 더 쨍해 보일까?

궁금증

디퓨즈 텍스처를 작성할 때, 또는 렌더링 된 장면의 채도를 Color grading 으로 조정했을 때, 유난히 붉은 계열 색상의 채도가 더 과장된 것 처럼 보일 때가 있다. 또는, 균일하게 채도를 낮췄음에도 푸른 계열 그늘에서 이미지가 너무 창백하거나 어둡게 느껴지는 때가 있다.

인간의 눈은 중간 밝기에 더욱 민감하게 반응한다고 전해지기 때문에, 중간 톤 밝기로 구성된, 색상이 풍부한 실사 이미지를 골라 테스트 해 보았다.

 

왼쪽 : 원본 이미지 / 오른쪽 : 채도를 50% 올려본 모습

모든 색상의 채도를 일정 수치만큼 올렸음에도, 붉은 계열 부분이 유난히 도드라져 보인다. 이것은 Clipping 또는 색번짐이라는 현상으로, 사진 보정 쪽에서 널리 쓰이는 용어인 것 같다. 해당 색상이 이미 높은 채도인 상황에서 채도를 높였을 때 나타나게 된다.

예시 : https://blog.naver.com/rheecastro/222693719037

포토샵에서의 Hue / Saturation 팔레트에서는 Saturation 값의 시작점이 0이고  -100부터 +100까지 조절할 수 있다.

어떤 이미지든 Saturation 값이 -100일 경우 채도가 완전히 빠진다. 부호는 +-이지만, HSL 컬러 스페이스에서 Saturation과 Lightness는 백분율 값이니, 현재 픽셀의 Saturation 값을 50% 중간지점이라고 봤을 때 슬라이더가 +100이라면, 각 픽셀의 Saturation 값은 HSL 공간에서 두 배 증가한다.

위 이미지에서는 Saturation 값을 +50으로 넣어주었으니, 각 픽셀 색상의 HSL 'S' 값은 1.5배 증가한 것이다.

 

 

또, 포토샵에는 Vibrance라는 효과도 있는데, 이 효과를 적용하면 색상에 관계없이 균일한 정도로 채도를 올려준다.

왼쪽 : 원본 이미지 / 오른쪽 : Vibrance +100 (2배 증가)

Saturation 에 비해 강도는 적게 느껴지지만, 이미지 채도가 균일하게 올라가면서도 전체적인 색상 균형이 깨지지는 않는 느낌이다. 특히 피부 톤에 가까운 색상들을 자연스럽게 보존하면서 이미지 전체의 채도를 올릴 수 있다고 한다.

Vibrance 값을 최소로 줬을 때

Vibrance 효과는 포토샵 CS4 버전부터 추가된 기능인데, Saturation과는 달리, 슬라이더의 최소값을 사용해도 색상이 완전히 회색조가 되지는 않는다.

Vibrance 패널에도 Saturation 이라는 슬라이더가 있지만, Hue/Saturation 패널의 슬라이더와는 동작 양상이 미묘하게 다르다.

출처 : https://www.photo-mark.com/notes/analyzing-photoshop-vibrance-and-saturation/

위 두 이미지는 Vibrance 패널의 Saturation 슬라이더를 사용한 것이고, 아래 두 이미지는 Hue/Saturation 패널의 Saturation 슬라이더를 사용한 것이다.

 

Vibrance 효과의 공식을 그대로 구현할 수 있다면 가장 좋겠지만, 공개된 내용을 찾을 수 없었다.

다만, 아래와 같은 인풋-아웃풋 채도를 비교한 그래프를 누군가 만들어 두었다.

그래프 출처 :https://www.photo-mark.com/notes/analyzing-photoshop-vibrance-and-saturation/

위 그래프는 원본 색상의 채도와, 효과 적용 이후의 채도를 컬러별로 비교한 그래프이다. 가로 축이 원본 이미지의 채도값, 세로축은 결과 이미지의 채도값이다. 각 점들의 색상은 원본 픽셀의 색상을 의미한다.

쉽게 말하면 45도 대각선에 가까울수록, 원본 색상의 채도에 가까운 것이다.

Saturation을 올렸을 때는 원본 색상 값에 관계없이 거의 일정한 비율로 채도가 올라갔고, 중간~높은 채도에 가까울수록 Vibrance 효과는 중간톤 색상의 output 채도 값이 각 채도와 다르게 적용된 것을 볼 수 있다. (그래프의 중간 부분을 보면 스펙트럼처럼 색상별 분화가 보임)

구글링 해 본 결과, Vibrance는 단순히 픽셀의 채도 값을 마스크로 사용해서 saturation 값을 차등 적용해 줄 뿐이라는 글을 찾았다.

https://dsp.stackexchange.com/questions/15785/how-could-one-imitate-photoshops-vibrance-filter-with-opencv

 

How Could One Imitate Photoshop's Vibrance Filter with OpenCV?

Can somebody please explain how Photoshop vibrance function can use in opencv c++ ? so that I may reproduce the same effects in my application. Here is the vibrance documentation

dsp.stackexchange.com

이 내용이 정확한지 한번 테스트해보았다.

Test 1. 원본 이미지의 채도 값으로 마스크 만들어 적용해보기

HLS 채널 중 L 채널을 반전시킴

Lightness가 아주 밝은 부분이나 너무 어두운 부분들은(threshold 이상 / 이하인 부분들은) 채도 계산에서 제외되도록 검은색으로 마스킹 해주었다. 이미지는 threshold 6으로 주었다. (밝기가 0~6이거나 249~255라면 검은 영역으로 처리)

 

위 이미지에서 흰색에 가까운 영역일수록 높은 채도이기 때문에  더 적은 비율로 채도 조정이 들어가야 한다. 반대로, 더 어두운 영역들은 채도의 변화가 거의 없도록 해볼 것이다. 위 이미지를 반전시킨 것을 마스크로 채도를 조절해 보았다.

 

왼쪽 : 원본 / 오른쪽 : 위 마스크를 사용해 채도를 +50으로 올려 본 모습

확실히 마스킹을 하지 않았을 때 보다는 색 번짐이 덜하다. 붉은 계열의 과한 느낌도 조금 사라졌다.

Vibrance와 완전히 같게 동작하지는 않지만, 이미지가 과해지는 느낌은 많이 줄어드는 것 같다.

마스크가 얼마나 섬세하느냐에 따라 결과 이미지가 달라질 수 있겠다.

왼쪽 : Vibrance값을 최소로 줬을 때 / 오른쪽 : 마스크를 사용해서 Saturation 값을 최소로 줬을 때

 

Test 2. 각 RGB값에 대해 채도 변화를 차등 적용해보기

이론에 따르면, 인간의 눈은 각 색상마다 받아들이는 밝기의 정도가 다르다. HSL 공간에서 L 값이 정확히 같은 픽셀이라도 Hue값에 따라 눈에서 느껴지는 밝기가 다르다는 뜻이다. 이를 반영한 컬러 스페이스가 CIE라는 기관에서 만든 Lab 색 공간이다..

https://en.wikipedia.org/wiki/CIELAB_color_space

 

CIELAB color space - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Standard color space with color-opponent values CIELAB color space top view CIELAB color space front view The CIE 1976 (L*, a*, b*) color space (CIELAB), showing only colors that fit w

en.wikipedia.org

Lab의 L 채널은 인간의 시지각에 가장 가까운 밝기로 구성된다고 한다.

RGB를 Lab 색 공간으로 변환하는 복잡한 공식이 있지만, 길게 알아볼 필요도 없이 OpenCV에 내장된 함수를 사용하여 변환할 수 있었다.

https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html

 

OpenCV: Color conversions

See cv::cvtColor and cv::ColorConversionCodes Todo:document other conversion modes RGB \(\leftrightarrow\) GRAY Transformations within RGB space like adding/removing the alpha channel, reversing the channel order, conversion to/from 16-bit RGB color (R5:G6

docs.opencv.org

원본 이미지에서 추출한 Luma 값

HSL 에서의 Lightness 값과는 얼마나 다를까? 비교해봤다.

왼쪽 : HLS의 L채널 / 오른쪽 : Lab의 L채널
왼쪽 : HLS의 L채널 / 오른쪽 : Lab의 L채널

미묘한 차이지만 색상 간의 명도 차이가 줄어든 부분들이 보인다. 중간 톤의 디테일들도 조금 더 풍부해졌다.

이 이미지를 흑백 반전시켜 (1-x) 마스크로 사용해서 채도를 조절해 보았다. (눈에 밝게 느껴지는 색상일수록 채도 조절이 덜 들어가게끔 해 본 것이다)

 

왼쪽 : 원본 / 오른쪽 : 마스크를 사용해 채도를 1.5배 올려본 모습

마스크가 사용하지 않고 채도를 올렸을 때와 비교해 보았다.

왼쪽 : 마스크 없이 채도 +50 / 오른쪽 : 반전된 Luma 마스크를 사용해 채도 +50

붉은 색 계열의 채도가 과장되는 부분이 확실히 줄어들었다. 다른 색들의 중간 톤 변화도 조금 더 안정적으로 보인다.

 

결론

  • 눈은 Luma 값이 큰 붉은색, 노란색 계열일수록 채도가 변경되었을 때 더 민감하게 반응한다.
  • 코드상에서는 보통 이미지의 색상 정보가 0.0에서 1.0 사이의 RGB 숫자 값에 담겨진다.
    이 숫자들끼리 곱하거나 덧셈, 뺄셈하는 것이 시각적으로 어떤 결과를 가져올 지 예측할 때, 색상마다 눈의 민감도가 다르기 때문에, 위에서 본 Luma 요소에 대한 부분도 반드시 고려해야 한다. 
  • 픽셀의 색 강도를 강하게 하거나 약하게 할 목적으로 텍스처와 색상, 텍스처와 텍스처를 blend 할 때는 특히 주의해야 한다. 화면에 이미 렌더링된 이미지의 밝기를 균일하게 올릴 때도 마찬가지이다.

 

아래는 포토샵 레이어 Multiply(곱셈) 블렌딩모드를 사용한 또 다른 테스트이다.

왼쪽 : 원본 / 오른쪽 : 원본 이미지의 제곱. 팔 그림자에 가려진 옷 주름이나, 모자 아레의 선수 얼굴 부분, 배경의 미묘한 중간 톤 색상 변화들을 구분하기 어려워 졌다.

오른쪽 이미지는  포토샵의 Multiply 기능을 이용해 왼쪽 이미지의 RGB값을 제곱한 것이다.

색상을 보다 강하게 할 목적으로 RGB 텍스처를 거듭 제곱하곤 하는데, RGB 값에 밝기 정보가 포함되어 있기 때문에 이렇게 제곱하게 되면 밝기 값이 어두운 부분과 밝은 부분들이 클리핑 된다. 중간 톤 색상 또한 밝기가 많이 어두워지기 때문에 전체적으로 이미지가 어두워지고 디테일 구분이 어려워진다. 만일 밝기는 되도록 그대로 보존하고, 이미지의 색상만 강하게 하고 싶다면, Luma 값이 높은 색상일수록 채도가 적게 올라가게끔 마스크로 차등을 두면 된다.

왼쪽 : 원본 / 오른쪽 : Luma 마스크를 사용해서 이미지를 3번 제곱한 모습

Luma 값으로 마스킹하면, 이미지 전체의 톤을 보존하면서도 색상 값만을 강화한 결과물을 얻을 수 있다.

글러브 윗부분이나 모자 윗부분처럼 빛을 받아 흐릿해진 부분들의 색상은 크게 강해진 반면, 중간 톤 색상들은 상대적으로 적은 채도 변화가 적다. 이 색상들은 채도가 조금만 변해도 눈이 민감하게 알아차리기 때문에, 전체적인 이미지의 채도는 균일하게 올라간 것 처럼 보인다.

 

 

덧. 굳이 복잡한 계산인 RGB -> Lab 계산을 하지 않고도, 각 RGB 채널의 눈에 대한 민감도로 가중치를 두어서 간단히 Luma 값을 추출하는 방법이 더 많이 사용되고 있는 것 같다. 컬러 이미지를 흑백처리 하고 싶을 때 사용된다.

출처 : https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color

 

Formula to determine perceived brightness of RGB color

I'm looking for some kind of formula or algorithm to determine the brightness of a color given the RGB values. I know it can't be as simple as adding the RGB values together and having higher sums...

stackoverflow.com

 

언리얼 Material 노드인 Desaturation 에서도 이런 식으로 값을 추출한다. (Color.h)

	/** Computes the perceptually weighted luminance value of a color. */
	inline float GetLuminance() const
	{		
		return R * 0.3f + G * 0.59f + B * 0.11f;
	}

이렇게 구한 픽셀의 Luminance 값을 완전히 Desaturation 된 값으로 두고, Desaturation Fraction(Percent) 값으로 원본 이미지~흑백 이미지 사이를 Lerp 한다.

https://docs.unrealengine.com/4.27/en-US/RenderingAndGraphics/Materials/ExpressionReference/Color/

 

Color Expressions

Expressions that perform actions on color inputs.

docs.unrealengine.com

이렇게 하면, Luma 값이 낮은 푸른 계열은 비교적 낮은 명도로, Luma 값이 높은 노란, 붉은 계열은 상대적으로 밝은 명도로 Desaturate 된다.