Post
High Dynamic Range(HDR) | Gihun Son

High Dynamic Range(HDR)

1. 금일 실습(HDR)을 수행하고 입력 영상들과 결과 영상(임의의 톤 맵 사용)의 히스토그램 분석(grayscale로 변환)을 통해 HDR의 효과를 분석할 것

1-1) 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <iostream>
#include<sstream>
#include "opencv2/core/core.hpp" // Mat class와 각종 data structure 및 산술 루틴을 포함하는 헤더
#include "opencv2/highgui/highgui.hpp" // GUI와 관련된 요소를 포함하는 헤더(imshow 등)
#include "opencv2/imgproc/imgproc.hpp" // 각종 이미지 처리 함수를 포함하는 헤더
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/stitching.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/photo.hpp>
#include <opencv2/imgcodecs.hpp>

using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;

Mat GetHistogram(Mat src) {
	Mat dst;
	cvtColor(src, dst, COLOR_BGR2GRAY);
	Mat histogram;
	const int* channel_numbers = { 0 };
	float channel_range[] = { 0.0, 255.0 };
	const float* channel_ranges = channel_range;
	int number_bins = 255;
	//히스토그램 계산
	calcHist(&dst, 1, channel_numbers, Mat(), histogram, 1, &number_bins, &channel_ranges);
	//히스토그램 plot
	int hist_w = 512;
	int hist_h = 400;
	int bin_w = cvRound((double)hist_w / number_bins);
	Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));
	normalize(histogram, histogram, 0, histImage.rows, NORM_MINMAX, -1, Mat()); //정규화
	for (int i = 1; i < number_bins; i++) {
		line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(histogram.at<float>(i - 1))),
			Point(bin_w * (i), hist_h - cvRound(histogram.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);// 값과 값을 잇는 선을 그리는 방식으로 plot
	}
	return histImage;
}

void readImagesAndTimes(vector<Mat>& images, vector<float>& times) {
	int numImages = 4;
	static const float timesArray[] = { 1 / 30.0f, 0.25f, 2.5f, 15.0f };
	times.assign(timesArray, timesArray + numImages);
	static const char* filenames[] = { "D:/study/디영처/14주차/14주차 실습 자료/hw1/img_0.033.jpg", "D:/study/디영처/14주차/14주차 실습 자료/hw1/img_0.25.jpg",
								"D:/study/디영처/14주차/14주차 실습 자료/hw1/img_2.5.jpg", "D:/study/디영처/14주차/14주차 실습 자료/hw1/img_15.jpg" };
	
	Mat his[4];

	for (int i = 0; i < numImages; i++) {
		Mat im = imread(filenames[i]);
		images.push_back(im);
		his[i] = GetHistogram(im);
	}

	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/his_0.033.jpg", his[0]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/his_0.25.jpg", his[1]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/his_2.5.jpg", his[2]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/his_15.jpg", his[3]);

	// < 영상 정렬 >
	cout << "Aligning images ..." << endl;
	Ptr<AlignMTB> alignMTB = createAlignMTB();
	alignMTB->process(images, images);

	// < Camera response function(CRF) 복원 >
	cout << "Calculating Camera Response Function ..." << endl;
	Mat responseDebevec;
	Ptr <CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
	calibrateDebevec->process(images, responseDebevec, times);
	cout << "------ CRF ------" << endl;
	cout << responseDebevec << endl;

	// < 24bit 표현 범위로 이미지 병합 >
	cout << "Merging images into one HDR image ..." << endl;
	Mat hdrDebevec;
	Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
	mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/hdrDebevec.hdr", hdrDebevec);
	cout << "saved hdrDebevec.hdr" << endl;
	/*
	cout << "Merging using Exposure Fusion ..." << endl;
	Mat hdrDebevec;
	Ptr<MergeMertens> mergeMertens = createMergeMertens();
	mergeMertens->process(images, hdrDebevec);
	*/

	// << Drago 톤맵 >>
	cout << "Tonemaping using Drago's method ..." << endl;
	Mat IdrDrago;
	Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.0f, 0.7f, 0.85f);
	tonemapDrago->process(hdrDebevec, IdrDrago);
	IdrDrago = 3 * IdrDrago;
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Idr-Drago.jpg", IdrDrago * 255);
	Mat Histo_Drago = GetHistogram(IdrDrago * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Histo_Drago.jpg", Histo_Drago);
	cout << "saved Idr-Drago.jpg" << endl;

	// << Reinhard 톤맵 >>
	cout << "Tonemaping using Reinhard's method ..." << endl;
	Mat IdrReinhard;
	Ptr<TonemapReinhard> tonemapReinhard = createTonemapReinhard(1.5f, 0, 0, 0);
	tonemapReinhard->process(hdrDebevec, IdrReinhard);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Idr-Reinhard.jpg", IdrReinhard * 255);
	Mat Histo_Reinhard = GetHistogram(IdrReinhard * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Histo_Reinhard.jpg", Histo_Reinhard);
	cout << "saved Idr-Reinhard.jpg" << endl;

	// << Mantiuk 톤맵 >>
	cout << "Tonemaping using Mantiuk's method ..." << endl;
	Mat IdrMantiuk;
	Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2f, 0.85f, 1.2f);
	tonemapMantiuk->process(hdrDebevec, IdrMantiuk);
	IdrMantiuk = 3 * IdrMantiuk;
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Idr-Mantiuk.jpg", IdrMantiuk * 255);
	Mat Histo_Mantiuk = GetHistogram(IdrMantiuk * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw1/Histo_Mantiuk.jpg", Histo_Mantiuk);
	cout << "saved Idr-Mantiuk.jpg" << endl;

}

int main() {
	cout << "Reading images and exposure times ..." << endl;
	vector<Mat> images;
	vector<float> times;

	readImagesAndTimes(images, times);
	cout << "finished" << endl;
	return 0;
}

1-2) 실험결과

[실험에 사용한 사진과 히스토그램]

  • img_0.033.jpg (image, histogram)

img_0 033

his_0 033

  • img_0.25.jpg (image, histogram)

img_0 25

his_0 25

  • img_2.5.jpg (image, histogram)

img_2 5

his_2 5

  • img_15.jpg (image, histogram)

img_15

his_15

[결과(Drago 톤맵)]

Idr-Drago

Histo_Drago

[결과(Reinhard 톤맵)]

Idr-Reinhard

Histo_Reinhard

[결과(Mantiuk 톤맵)]

Idr-Mantiuk

Histo_Mantiuk

1-3)구현 과정

  • Mat GetHistogram(Mat src) : GetHistogram함수는 이미지의 histogram결과를 보기 위한 함수로, image를 gray scale로 변환하여 나타내는 것 이외에는 3주차 과제에서 구현했던 함수와 동일한 함수입니다.
  • void readImagesAndTimes(vector<Mat>& images, vector<float>& times) : **reagImageAndTimes함수는 HDR을 적용하기 위해 코딩된 함수입니다. 동작 순서는 다음과 같습니다.
    1. 노출 시간이 각각 다른 이미지를 저장하고, 노출 시간을 저장합니다.
    2. cv::AlighMTB를 사용하여 영상을 정렬합니다.
    3. cv::CalibrateDebevec를 사용하여 CRF(Camera response function)을 복원합니다.
    4. 넓은 범위에 대해 영상을 병합하기 위해 cv::MergeDebevec을 사용하여 24bit 표현 범위로 영상을 병합합니다.
    5. 24bit는 컴퓨터가 출력할 수 없어 톤 매핑 기법을 적용합니다. 여기서 다양한 톤 매핑 기법들이 존재하는데, Drago, Reinhard, Mantiuk의 총 3가지 톤 매핑 기법을 통해 결과를 확인합니다.**

1-4)결과 분석

히스토그램에서 왼쪽이 어두운 픽셀, 오른쪽이 밝은 픽셀을 의미합니다. 이때, 노출시간이 작은 영상은 육안으로 보기에도 어둡지만, 히스토그램을 확인해본다면 어두운 픽셀들이 많은 것을 확인할 수 있었습니다. 또한 노출시간이 긴 영상은 밝은 픽셀들이 많이 분포한 것을 볼 수 있었습니다. 이 때 노출시간이 다양한 이러한 영상들을 readImagesAndTimes 함수를 통해 HDR을 적용하였습니다.

이때 3가지 톤 맵핑 기법, Drago, Reinhard, Mantiuk을 사용하였습니다. 각 기법을 적용한 영상의 히스토그램을 분석해보았습니다. 우선 사진을 보면 노출시간이 다른 각 사진들에 비해 밝기 표현이 정확하게 되었다는 생각을 했습니다. 노출이 길면 밝은 부분이 잘 표현되지 않았고, 짧으면 어두운 부분이 잘 표현되지 않았는데, 이러한 한계점을 극복한 모습을 볼 수 있었습니다. 즉 더 넓은 밝기 범위가 표시되었다는 것을 알 수 있었습니다.

히스토그램을 통해서는 더욱 명확하게 알 수 있었습니다. 3가지 톤 맵핑 기법을 적용한 사진의 히스토그램 모두 우선 pixel들의 대비가 커진 것을 볼 수 있었습니다. 사진 자체가 밤에 찍은 것이기 때문에 어두운 픽셀들이 더 많았지만, 밝은 부분을 표현할 수 있는 픽셀들도 대비가 높게 잘 형성되었습니다. 따라서 사진이 훨씬 선명하게 느껴지는 것을 육안으로도 확인할 수 있었습니다. 히스토그램의 모양도 3가지 기법 모두 비슷하게 나왔지만, 조금의 차이가 있었는데, 이 차이로 인해 사진들을 볼 때의 느낌도 조금은 다르게 느껴진 것 같습니다.

Drago기법은 히스토그램에서 밝기가 높은 픽셀의 비율이 상대적으로 높았는데, 이 때문에 빛이 있는 부분, 즉 밝은 부분이 잘 표현되고 상대적으로 넓은 범위의 밝기값을 갖는다고 느꼈습니다.

Reinhard기법은 상대적으로 밝기가 낮은 픽셀들이 많아, 밝은 부분을 잘 살리면서 밤의 어두움을 잘 표현할 수 있었던 것 같습니다. 다른 기법들보다 조금 더 어둡게 느껴졌습니다.

마지막으로 Mantiuk기법은 Drago와 Reinhard의 중간 밝기 값을 많이 가지고 있어 비교적 편안해보이는 image를 얻을 수 있었던 것 같습니다.

2. 본인 카메라를 이용해 다양한 노출의 영상을 직접 촬영해보고 금일 실습의 함수들을 적용해볼 것

2-1) 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <iostream>
#include<sstream>
#include "opencv2/core/core.hpp" // Mat class와 각종 data structure 및 산술 루틴을 포함하는 헤더
#include "opencv2/highgui/highgui.hpp" // GUI와 관련된 요소를 포함하는 헤더(imshow 등)
#include "opencv2/imgproc/imgproc.hpp" // 각종 이미지 처리 함수를 포함하는 헤더
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/stitching.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/photo.hpp>
#include <opencv2/imgcodecs.hpp>

using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;

Mat GetHistogram(Mat src) {
	Mat dst;
	cvtColor(src, dst, COLOR_BGR2GRAY);
	Mat histogram;
	const int* channel_numbers = { 0 };
	float channel_range[] = { 0.0, 255.0 };
	const float* channel_ranges = channel_range;
	int number_bins = 255;
	//히스토그램 계산
	calcHist(&dst, 1, channel_numbers, Mat(), histogram, 1, &number_bins, &channel_ranges);
	//히스토그램 plot
	int hist_w = 512;
	int hist_h = 400;
	int bin_w = cvRound((double)hist_w / number_bins);
	Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));
	normalize(histogram, histogram, 0, histImage.rows, NORM_MINMAX, -1, Mat()); //정규화
	for (int i = 1; i < number_bins; i++) {
		line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(histogram.at<float>(i - 1))),
			Point(bin_w * (i), hist_h - cvRound(histogram.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);// 값과 값을 잇는 선을 그리는 방식으로 plot
	}
	return histImage;
}

void readImagesAndTimes(vector<Mat>& images, vector<float>& times) {
	int numImages = 4;
	static const float timesArray[] = { 1 / 30.0f, 0.25f, 2.5f, 15.0f };
	times.assign(timesArray, timesArray + numImages);
	static const char* filenames[] = { "D:/study/디영처/14주차/14주차 실습 자료/hw2/img_0.033.jpg", "D:/study/디영처/14주차/14주차 실습 자료/hw2/img_0.25.jpg",
								"D:/study/디영처/14주차/14주차 실습 자료/hw2/img_2.5.jpg", "D:/study/디영처/14주차/14주차 실습 자료/hw2/img_15.jpg" };

	Mat his[4];

	for (int i = 0; i < numImages; i++) {
		Mat im = imread(filenames[i]);
		images.push_back(im);
		his[i] = GetHistogram(im);
	}

	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/his_0.033.jpg", his[0]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/his_0.25.jpg", his[1]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/his_2.5.jpg", his[2]);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/his_15.jpg", his[3]);

	// < 영상 정렬 >
	cout << "Aligning images ..." << endl;
	Ptr<AlignMTB> alignMTB = createAlignMTB();
	alignMTB->process(images, images);

	// < Camera response function(CRF) 복원 >
	cout << "Calculating Camera Response Function ..." << endl;
	Mat responseDebevec;
	Ptr <CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
	calibrateDebevec->process(images, responseDebevec, times);
	cout << "------ CRF ------" << endl;
	cout << responseDebevec << endl;

	// < 24bit 표현 범위로 이미지 병합 >
	cout << "Merging images into one HDR image ..." << endl;
	Mat hdrDebevec;
	Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
	mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/hdrDebevec.hdr", hdrDebevec);
	cout << "saved hdrDebevec.hdr" << endl;
	/*
	cout << "Merging using Exposure Fusion ..." << endl;
	Mat hdrDebevec;
	Ptr<MergeMertens> mergeMertens = createMergeMertens();
	mergeMertens->process(images, hdrDebevec);
	*/

	// << Drago 톤맵 >>
	cout << "Tonemaping using Drago's method ..." << endl;
	Mat IdrDrago;
	Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.0f, 0.7f, 0.85f);
	tonemapDrago->process(hdrDebevec, IdrDrago);
	IdrDrago = 3 * IdrDrago;
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Idr-Drago.jpg", IdrDrago * 255);
	Mat Histo_Drago = GetHistogram(IdrDrago * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Histo_Drago.jpg", Histo_Drago);
	cout << "saved Idr-Drago.jpg" << endl;

	// << Reinhard 톤맵 >>
	cout << "Tonemaping using Reinhard's method ..." << endl;
	Mat IdrReinhard;
	Ptr<TonemapReinhard> tonemapReinhard = createTonemapReinhard(1.5f, 0, 0, 0);
	tonemapReinhard->process(hdrDebevec, IdrReinhard);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Idr-Reinhard.jpg", IdrReinhard * 255);
	Mat Histo_Reinhard = GetHistogram(IdrReinhard * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Histo_Reinhard.jpg", Histo_Reinhard);
	cout << "saved Idr-Reinhard.jpg" << endl;

	// << Mantiuk 톤맵 >>
	cout << "Tonemaping using Mantiuk's method ..." << endl;
	Mat IdrMantiuk;
	Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2f, 0.85f, 1.2f);
	tonemapMantiuk->process(hdrDebevec, IdrMantiuk);
	IdrMantiuk = 3 * IdrMantiuk;
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Idr-Mantiuk.jpg", IdrMantiuk * 255);
	Mat Histo_Mantiuk = GetHistogram(IdrMantiuk * 255);
	imwrite("D:/study/디영처/14주차/14주차 실습 자료/hw2/Histo_Mantiuk.jpg", Histo_Mantiuk);
	cout << "saved Idr-Mantiuk.jpg" << endl;

}

int main() {
	cout << "Reading images and exposure times ..." << endl;
	vector<Mat> images;
	vector<float> times;

	readImagesAndTimes(images, times);
	cout << "finished" << endl;
	return 0;
}

2-2) 실험 결과

[실험에 사용한 사진과 히스토그램]

  • img_0.033.jpg (image, histogram)

img_0 033 1

his_0 033 1

  • img_0.25.jpg (image, histogram)

img_0 25 1

his_0 25 1

  • img_2.5.jpg (image, histogram)

Alt text

his_2 5 1

  • img_15.jpg (image, histogram)

Alt text

his_15 1

[결과(Drago 톤맵)]

Idr-Drago 1

Histo_Drago 1

[결과(Reinhard 톤맵)]

Idr-Reinhard 1

Histo_Reinhard 1

[결과(Mantiuk 톤맵)]

Idr-Mantiuk 1

Histo_Mantiuk 1

2-3) 구현 과정(코드 설명)

  • 코드는 사진의 경로를 제외하고 모두 동일합니다. 다만, 사진을 취득한 후, 해당 사진의 노출시간을 확인하여 바꿔서 결과를 확인하였는데 정확한 결과가 나오지 않았습니다. 따라서 과제 1번과 동일한 파라미터 값으로 실험을 진행하였고, 위와 같은 결과를 도출할 수 있었습니다.

2-4) 결과 분석

과제 2번은 직접 찍은 사진을 1번과 같이 활용하였습니다. 결과 또한 전문적으로 찍힌 사진을 사용한 것보다 깔끔하게 나오지는 않았지만, 1번과제에서 분석한 결과대로 도출되었습니다.

다만 강의노트에서 ‘영상 정렬’ 설명 부분에서도 나왔듯이, 코딩을 통한 영상 정렬보다 삼각대와 같은 도구를 통해 고정된 상태에서 영상을 취득하는 것이 더 중요하지만, 삼각대를 구할 수 없었고 빛이 적당히 존재하는 환경을 찍어서 HDR의 효과를 보다 확실하게 확인하기 위해 위와 같은 사진을 취득하였습니다. 이 때문에 영상이 정렬이 완벽하게 되지 않아 다소 화면이 깨져보이는 현상은 있지만, HDR의 효과를 확인하는 것에는 문제가 없었습니다.

저는 과제 1번과 같이 빛이 나오는 환경의 사진을 노출시간을 달리하여 실습해보고 싶었습니다. 따라서 노트북과 컴퓨터 화면이 켜져있는 책상을 실습 사진으로 사용하였습니다. 원래 사진의 히스토그램을 보면 노출시간에 따라 밝기 픽셀이 다르게 분포되어있는 것을 과제1번과 같이 확인할 수 있었습니다.

이를 각각의 톤맵 기법을 활용하여 실험결과를 확인하였는데, 우선 사진을 보면 노출시간이 작은 사진에서는 표현되지 않았던 모니터 외의 어두운 부분과 노출시간이 긴 사진에서 포함되지 않았던 밝은 모니터의 디테일이 모두 구현된 것을 볼 수 있었습니다. 이를 통해 과제1번과 같이 깔끔한 결과는 아니지만, 밝기의 표현 범위를 늘려 개선했다는 것을 알 수 있었습니다.

히스토그램을 살펴보았을 때에도, 노출 시간에 따라 서로 다른 히스토그램 분포를 가졌던 기법 적용 전 사진들에 비해, 밝기값이 높고 낮은 픽셀들이 모두 분포하고 대비도가 높다는 것을 확인하였습니다. 과제 1번에서 분석했던 각 기법들의 결과에 대한 느낌은 다소 다르지만, 그래도 3가지 기법 모두 밝기가 개선되도록 잘 적용되었다는 것을 알 수 있었습니다.

[고찰]

이번 실습은 HDR을 코딩을 통해 구현해보는 과제였습니다. 사실 영상처리를 배우기 전까지 영상과 실제 3D환경의 색상이 다를 것이라고 생각하지 못하였습니다. 하지만 생각을 해보니, 영상을 찍는 환경에 따라 밝고 어두운 부분이 달라지고, 무엇보다 영상은 0-255까지의 범위로 영상을 표현할 수 있다는 한계가 있다는 것을 알 수 있었습니다. 이를 알고리즘을 통해 극복하는 것이 이번 실습에서 적용한 방법의 이론적 목표인데, 실습을 하기 전까지는 생각보다 좋지 않은 결과가 나올 것이라고 예상했습니다. 하지만 실제 결과를 분석해보았을 때, 흥미로울만큼 괜찮은 성능으로 HDR을 표현할 수 있다는 것을 알 수 있었고, 실습에서 주어진 사진 뿐만 아니라, 직접 찍은 사진으로 밝기를 개선함으로써 이론적으로만 다루었던 HDR의 적용 방법과 효과를 직관적으로 느낄 수 있었던 실습이었습니다.

This post is licensed under CC BY 4.0 by the author.