컴퓨터 사진에서의 Python 이미지 처리 소개
게시 됨: 2022-03-11컴퓨터 사진은 컴퓨터를 사용하여 사진 프로세스를 향상시키는 것입니다. 우리는 일반적으로 이것이 최종 결과(사진 편집과 유사) 후처리에만 적용된다고 생각하는 경향이 있지만, 장면 조명으로 시작하여 렌즈, 그리고 결국에는 캡처된 이미지의 디스플레이에도 적용됩니다.
이것은 일반 카메라로 달성할 수 있는 것보다 훨씬 더 다양한 방식으로 수행할 수 있기 때문에 중요합니다. 또한 오늘날 가장 널리 사용되는 카메라 유형인 모바일 카메라가 더 큰 형제(DSLR)에 비해 특별히 강력하지는 않지만 장치에서 사용할 수 있는 컴퓨팅 성능을 활용하여 좋은 작업을 수행하기 때문에 중요합니다. .
계산을 통해 사진을 향상시킬 수 있는 두 가지 예를 살펴보겠습니다. 더 정확하게는 단순히 더 많은 사진을 찍고 약간의 Python을 사용하여 결합하는 방법이 모바일 카메라 하드웨어가 지원하지 않는 두 가지 상황에서 좋은 결과를 생성할 수 있다는 점입니다. 정말 빛납니다. 저조도와 높은 다이내믹 레인지.
저조도 사진
장면의 저조도 사진을 찍고 싶지만 카메라의 조리개(렌즈)가 작고 노출 시간이 제한되어 있다고 가정해 보겠습니다. 이것은 조명이 약한 장면에서 다음과 같은 이미지를 생성할 수 있는 휴대폰 카메라의 일반적인 상황입니다(iPhone 6 카메라로 촬영).
대비를 개선하려고 하면 결과는 다음과 같이 매우 나쁩니다.
무슨 일이야? 이 모든 소음은 어디에서 오는 것입니까?
대답은 노이즈가 센서에서 발생한다는 것입니다. 센서는 빛이 닿는 시간과 빛의 강도를 결정하는 장치입니다. 그러나 저조도에서는 무엇이든 등록하려면 감도를 크게 높여야 하며, 감도가 높다는 것은 단순히 존재하지 않는 광자(거짓 양성자)도 감지하기 시작한다는 것을 의미합니다. (참고로 이 문제는 장치뿐만 아니라 우리 인간에게도 영향을 미칩니다. 다음 번에 어두운 방에 있을 때 잠시 시간을 내어 시야에 존재하는 소음을 알아차리십시오.)
어느 정도의 노이즈는 이미징 장치에 항상 존재합니다. 그러나 신호(유용한 정보)의 강도가 높으면 노이즈는 무시할 수 있습니다(높은 신호 대 노이즈 비율). 신호가 낮을 때(예: 저조도에서) 노이즈가 두드러집니다(낮은 신호 대 노이즈).
그럼에도 불구하고 우리는 위의 것보다 더 나은 샷을 얻기 위해 모든 카메라 제한에도 불구하고 노이즈 문제를 극복할 수 있습니다.
그렇게 하려면 시간이 지남에 따라 어떤 일이 발생하는지 고려해야 합니다. 신호는 동일하게 유지되지만(같은 장면이고 정적이라고 가정) 노이즈는 완전히 무작위입니다. 즉, 장면을 많이 촬영하면 노이즈의 버전은 다르지만 유용한 정보는 동일합니다.
따라서 시간이 지남에 따라 촬영한 많은 이미지의 평균을 구하면 신호에 영향을 주지 않으면서 노이즈가 제거됩니다.
다음 그림은 간단한 예를 보여줍니다. 노이즈의 영향을 받는 신호(삼각형)가 있고 다른 노이즈의 영향을 받는 동일한 신호의 여러 인스턴스를 평균화하여 신호를 복구하려고 합니다.
노이즈가 단일 인스턴스에서 신호를 완전히 왜곡할 만큼 충분히 강하지만 평균화하면 노이즈가 점진적으로 감소하고 원래 신호를 복구한다는 것을 알 수 있습니다.
이 원칙이 이미지에 어떻게 적용되는지 살펴보겠습니다. 먼저 카메라가 허용하는 최대 노출로 피사체를 여러 번 촬영해야 합니다. 최상의 결과를 얻으려면 수동 촬영이 가능한 앱을 사용하세요. 같은 위치에서 촬영하는 것이 중요하므로 (즉석) 삼각대가 도움이 될 것입니다.
더 많은 샷은 일반적으로 더 나은 품질을 의미하지만 정확한 숫자는 상황에 따라 다릅니다. 빛의 양, 카메라의 민감도 등입니다. 좋은 범위는 10에서 100 사이가 될 수 있습니다.
이러한 이미지가 있으면(가능한 경우 원시 형식으로) Python에서 읽고 처리할 수 있습니다.
Python의 이미지 처리에 익숙하지 않은 사람들을 위해 이미지는 바이트 값(0-255)의 2D 배열로 표현된다는 점을 언급해야 합니다. 즉, 흑백 또는 회색조 이미지의 경우입니다. 컬러 이미지는 각 컬러 채널(R, G, B)에 대해 하나씩 또는 수직 위치, 수평 위치 및 컬러 채널(0, 1, 2)로 인덱스화된 3D 배열인 3개의 이미지 세트로 생각할 수 있습니다. .
NumPy(http://www.numpy.org/) 및 OpenCV(https://opencv.org/)의 두 라이브러리를 사용할 것입니다. 첫 번째는 배열에 대한 계산을 매우 효과적으로(놀라울 정도로 짧은 코드로) 수행할 수 있도록 하는 반면 OpenCV는 이 경우 이미지 파일의 읽기/쓰기를 처리하지만 훨씬 더 많은 고급 그래픽 절차를 제공할 수 있습니다. 기사의 뒷부분에서 사용하십시오.
import os import numpy as np import cv2 folder = 'source_folder' # We get all the image files from the source folder files = list([os.path.join(folder, f) for f in os.listdir(folder)]) # We compute the average by adding up the images # Start from an explicitly set as floating point, in order to force the # conversion of the 8-bit values from the images, which would otherwise overflow average = cv2.imread(files[0]).astype(np.float) for file in files[1:]: image = cv2.imread(file) # NumPy adds two images element wise, so pixel by pixel / channel by channel average += image # Divide by count (again each pixel/channel is divided) average /= len(files) # Normalize the image, to spread the pixel intensities across 0..255 # This will brighten the image without losing information output = cv2.normalize(average, None, 0, 255, cv2.NORM_MINMAX) # Save the output cv2.imwrite('output.png', output)
결과(자동 대비가 적용된 상태)는 노이즈가 사라지고 원본 이미지에서 매우 크게 개선되었음을 보여줍니다.
그러나 우리는 여전히 녹색 프레임과 격자 모양 패턴과 같은 이상한 인공물을 발견합니다. 이번에는 랜덤 노이즈가 아니라 고정 패턴 노이즈입니다. 무슨 일이에요?
다시, 우리는 그것을 센서에 탓할 수 있습니다. 이 경우 센서의 다른 부분이 빛에 다르게 반응하여 가시적인 패턴이 나타나는 것을 볼 수 있습니다. 이러한 패턴의 일부 요소는 규칙적이며 대부분 센서 기판(금속/실리콘) 및 입사 광자를 반사/흡수하는 방식과 관련이 있습니다. 흰색 픽셀과 같은 다른 요소는 단순히 결함이 있는 센서 픽셀로, 빛에 지나치게 민감하거나 너무 둔감할 수 있습니다.
다행히도 이러한 유형의 소음을 제거하는 방법이 있습니다. 다크 프레임 빼기라고 합니다.
그러려면 패턴 노이즈 자체의 이미지가 필요하며, 이는 어둠을 촬영하면 얻을 수 있습니다. 네, 맞습니다. 카메라 구멍을 덮고 최대 노출 시간과 ISO 값으로 많은 사진(예: 100)을 찍고 위에서 설명한 대로 처리하면 됩니다.
많은 블랙 프레임(임의 노이즈로 인해 실제로 블랙이 아님)에 대해 평균을 낼 때 고정 패턴 노이즈로 끝납니다. 이 고정 노이즈가 일정하게 유지된다고 가정할 수 있으므로 이 단계는 한 번만 필요합니다. 결과 이미지는 향후의 모든 저조도 촬영에 재사용할 수 있습니다.
다음은 iPhone 6에서 패턴 노이즈(대비 조정됨)의 오른쪽 상단 부분이 어떻게 보이는지 보여줍니다.
다시 한 번, 우리는 격자 모양의 질감과 흰색 픽셀이 붙어 있는 것처럼 보이는 것까지 알 수 있습니다.
이 다크 프레임 노이즈의 값( average_noise
변수)이 있으면 정규화하기 전에 지금까지의 샷에서 간단히 뺄 수 있습니다.
average -= average_noise output = cv2.normalize(average, None, 0, 255, cv2.NORM_MINMAX) cv2.imwrite('output.png', output)
다음은 최종 사진입니다.

높은 동적 범위
소형(모바일) 카메라의 또 다른 한계는 동적 범위가 작다는 것입니다. 즉, 세부 사항을 캡처할 수 있는 빛의 강도 범위가 다소 좁습니다.
즉, 카메라는 장면에서 좁은 범위의 빛 강도만 캡처할 수 있습니다. 해당 밴드 아래의 명암도는 순수한 검은색으로 표시되고 그 위의 명암도는 순수한 흰색으로 나타나며 해당 영역에서 모든 세부 사항이 손실됩니다.
그러나 카메라(또는 사진작가)가 사용할 수 있는 트릭이 있습니다. 즉, 센서에 도달하는 총 빛의 양을 효과적으로 제어하기 위해 노출 시간(센서가 빛에 노출되는 시간)을 조정하는 것입니다. 주어진 장면에 가장 적합한 범위를 캡처하기 위해 범위를 위 또는 아래로 이동합니다.
그러나 이것은 타협입니다. 많은 세부 사항이 최종 사진에 포함되지 않습니다. 아래 두 이미지에서 우리는 노출 시간이 다른 동일한 장면을 볼 수 있습니다: 매우 짧은 노출(1/1000초), 중간 노출(1/50초) 및 긴 노출(1/4초).
보시다시피 세 이미지 중 어느 것도 사용 가능한 모든 세부 사항을 캡처할 수 없습니다. 램프의 필라멘트는 첫 번째 샷에서만 볼 수 있고 꽃 세부 사항 중 일부는 중간이나 마지막 샷에서 볼 수 있지만 그렇지 않습니다. 둘 다.
좋은 소식은 이에 대해 우리가 할 수 있는 일이 있다는 것입니다. 다시 말하지만 약간의 Python 코드로 여러 장면을 구축해야 합니다.
우리가 취할 접근 방식은 여기 그의 논문에서 방법을 설명하는 Paul Debevec et al.의 작업을 기반으로 합니다. 이 방법은 다음과 같이 작동합니다.
첫째, 동일한 장면(고정)을 여러 번 촬영해야 하지만 노출 시간은 다릅니다. 다시 말하지만 이전의 경우와 마찬가지로 카메라가 전혀 움직이지 않도록 삼각대나 지지대가 필요합니다. 노출 시간을 제어하고 카메라 자동 조정을 방지할 수 있도록 수동 촬영 앱(전화를 사용하는 경우)도 필요합니다. 필요한 샷 수는 이미지에 있는 강도의 범위(3개 이상)에 따라 다르며 노출 시간은 해당 범위에 걸쳐 간격을 두어 보존하려는 세부 사항이 적어도 한 번의 샷에서 명확하게 표시되도록 해야 합니다.
다음으로, 알고리즘은 서로 다른 노출 시간에 걸쳐 동일한 픽셀의 색상을 기반으로 카메라의 응답 곡선을 재구성하는 데 사용됩니다. 이를 통해 기본적으로 포인트의 실제 장면 밝기, 노출 시간 및 캡처된 이미지에서 해당 픽셀이 가질 값 사이의 맵을 설정할 수 있습니다. OpenCV 라이브러리에서 Debevec의 메소드 구현을 사용할 것입니다.
# Read all the files with OpenCV files = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg'] images = list([cv2.imread(f) for f in files]) # Compute the exposure times in seconds exposures = np.float32([1. / t for t in [1000, 500, 100, 50, 10]]) # Compute the response curve calibration = cv2.createCalibrateDebevec() response = calibration.process(images, exposures)
응답 곡선은 다음과 같습니다.
세로 축에는 한 지점의 장면 밝기와 노출 시간의 누적 효과가 있고 가로 축에는 해당 픽셀이 가질 값(채널당 0 ~ 255)이 있습니다.
이 곡선을 통해 역 작업(프로세스의 다음 단계)을 수행할 수 있습니다. 픽셀 값과 노출 시간이 주어지면 장면의 각 지점의 실제 밝기를 계산할 수 있습니다. 이 밝기 값을 irradiance라고 하며 단위 센서 영역에 떨어지는 빛 에너지의 양을 측정합니다. 이미지 데이터와 달리 훨씬 더 넓은 범위의 값(따라서 높은 동적 범위)을 반영하기 때문에 부동 소수점 숫자를 사용하여 표현됩니다. 조도 이미지(HDR 이미지)가 있으면 간단히 저장할 수 있습니다.
# Compute the HDR image merge = cv2.createMergeDebevec() hdr = merge.process(images, exposures, response) # Save it to disk cv2.imwrite('hdr_image.hdr', hdr)
HDR 디스플레이(점점 더 보편화되고 있음)를 가질 만큼 운이 좋은 사람들에게는 이 이미지를 모든 영광으로 직접 시각화하는 것이 가능할 수 있습니다. 불행히도 HDR 표준은 아직 초기 단계이므로 이를 수행하는 프로세스는 디스플레이마다 다소 다를 수 있습니다.
나머지 사람들에게 좋은 소식은 이 데이터를 계속 활용할 수 있다는 것입니다. 일반 디스플레이에서는 이미지에 바이트 값(0-255) 채널이 있어야 합니다. irradiance map의 풍부함을 어느 정도 포기해야 하지만, 적어도 우리는 그것을 하는 방법에 대한 통제권을 가지고 있습니다.
이 프로세스를 톤 매핑이라고 하며 부동 소수점 조도 맵(높은 범위의 값 포함)을 표준 바이트 값 이미지로 변환하는 작업이 포함됩니다. 많은 추가 세부 사항이 보존되도록 하는 기술이 있습니다. 이것이 어떻게 작동하는지에 대한 예를 제공하기 위해 부동 소수점 범위를 바이트 값으로 압축하기 전에 HDR 이미지에 있는 가장자리를 향상(선명하게)한다고 상상해 보십시오. 이러한 가장자리를 강화하면 낮은 동적 범위 이미지에서도 가장자리를 보존하는 데 도움이 됩니다.
OpenCV는 Drago, Durand, Mantiuk 또는 Reinhardt와 같은 톤 매핑 연산자 세트를 제공합니다. 다음은 이러한 연산자(Durand) 중 하나를 사용할 수 있는 방법과 생성되는 결과의 예입니다.
durand = cv2.createTonemapDurand(gamma=2.5) ldr = durand.process(hdr) # Tonemap operators create floating point images with values in the 0..1 range # This is why we multiply the image with 255 before saving cv2.imwrite('durand_image.png', ldr * 255)
프로세스를 더 많이 제어해야 하는 경우 Python을 사용하여 고유한 연산자를 만들 수도 있습니다. 예를 들어, 이것은 값 범위를 8비트로 축소하기 전에 매우 적은 픽셀로 표현되는 강도를 제거하는 사용자 정의 연산자로 얻은 결과입니다(자동 대비 단계가 뒤따름).
위의 연산자에 대한 코드는 다음과 같습니다.
def countTonemap(hdr, min_fraction=0.0005): counts, ranges = np.histogram(hdr, 256) min_count = min_fraction * hdr.size delta_range = ranges[1] - ranges[0] image = hdr.copy() for i in range(len(counts)): if counts[i] < min_count: image[image >= ranges[i + 1]] -= delta_range ranges -= delta_range return cv2.normalize(image, None, 0, 1, cv2.NORM_MINMAX)
결론
우리는 약간의 Python과 몇 가지 지원 라이브러리를 사용하여 최종 결과를 개선하기 위해 물리적 카메라의 한계를 뛰어 넘을 수 있는 방법을 보았습니다. 우리가 논의한 두 가지 예는 더 나은 것을 만들기 위해 여러 개의 저품질 샷을 사용하지만 다른 문제와 제한 사항에 대한 다른 많은 접근 방식이 있습니다.
많은 카메라 폰에 이러한 특정 예를 다루는 스토어 또는 내장 앱이 있지만, 이를 손으로 프로그래밍하고 얻을 수 있는 더 높은 수준의 제어 및 이해를 즐기는 것은 분명히 전혀 어렵지 않습니다.
모바일 장치에서 이미지 계산에 관심이 있다면 동료 Toptaler와 엘리트 OpenCV 개발자 Altaibayar Tseveenbayar가 작성한 OpenCV Tutorial: iOS에서 MSER를 사용한 실시간 객체 감지를 확인하십시오.