{"id":9,"date":"2026-11-12T20:24:17","date_gmt":"2026-11-12T20:24:17","guid":{"rendered":"https:\/\/blog.metu.edu.tr\/e160477\/?p=9"},"modified":"2026-11-12T20:24:17","modified_gmt":"2026-11-12T20:24:17","slug":"ceng-587-hw1-comparing-debevec-malik-and-mitsunaga-nayar","status":"publish","type":"post","link":"https:\/\/blog.metu.edu.tr\/e160477\/2026\/11\/12\/ceng-587-hw1-comparing-debevec-malik-and-mitsunaga-nayar\/","title":{"rendered":"CENG 587 HW1: Comparing Debevec &amp; Malik and Mitsunaga &amp; Nayar"},"content":{"rendered":"<h2><\/h2>\n<div>\n<div>This work is about comparing two algorithms of generating HDR image. To generate HDR image different exposed images are combined. A camera creates images by converting real light and exposure time into digital level via non linear function which is called the camera response function (CRF):<\/div>\n<\/div>\n<p>Z = f(E \u00b7 \u0394t)<\/p>\n<div>\n<div>Then to form HDR image from different exposed images, one needs to know the CRF to inverse the process and obtain the light value for each pixel.<\/div>\n<\/div>\n<p>In this homework I compared two methods that find f\u207b\u00b9 from photos taken with different exposure times:<\/p>\n<ul>\n<li><strong>Debevec &amp; Malik (1997):<\/strong> It does not assume a formula for the curve. It finds all 256 values of g = ln f\u207b\u00b9 by solving one big linear system. There is a smoothness term with a weight \u03bb, so the curve does not become noisy. It needs the exposure times.<\/li>\n<li><strong>Mitsunaga &amp; Nayar (1999):<\/strong> It assumes f\u207b\u00b9 is a polynomial of order N. It only needs to find N+1 coefficients, so it is fast. It can also estimate the exposure ratios, so the exact exposure times are not necessary.<\/li>\n<\/ul>\n<h2><\/h2>\n<p>I chose <strong>Option 2<\/strong>. I compared <strong>two algorithms<\/strong>. For Debevec &amp; Malik I used two different implementations and for Mitsunaga &amp; Nayar one, so there are three versions in total:<\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>Name<\/th>\n<th>Algorithm<\/th>\n<th>Source<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Debevec (OpenCV)<\/td>\n<td>Debevec &amp; Malik<\/td>\n<td>OpenCV <code>cv2.createCalibrateDebevec<\/code> (70 samples, \u03bb = 10)<\/td>\n<\/tr>\n<tr>\n<td>Debevec (Piccante)<\/td>\n<td>Debevec &amp; Malik<\/td>\n<td>Piccante HDR library (github.com\/banterle\/piccante), function <code>gsolve<\/code>, converted from C++ to Python with the help of an AI assistant (256 samples, \u03bb = 20)<\/td>\n<\/tr>\n<tr>\n<td>Mitsunaga (Piccante)<\/td>\n<td>Mitsunaga &amp; Nayar<\/td>\n<td>Piccante, function <code>MitsunagaNayarClassic<\/code>, converted from C++ to Python with the help of an AI assistant (order N chosen from 1\u20133)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>I checked the Python versions by testing them on images with a known response curve (the virtual camera), and they recovered the correct curve.<\/p>\n<p>Some details:<br \/>\n&#8211; <strong>Sample selection.<\/strong> The Piccante versions choose sample pixels with histograms (the Grossberg\u2013Nayar idea): the brightness order of the pixels is the same in every photo, so the same percentile belongs to the same scene point. OpenCV picks 70 random pixel positions instead.<br \/>\n&#8211; <strong>Same merging for all.<\/strong> I merged every curve to an HDR image with the same OpenCV function (<code>MergeDebevec<\/code>) and tone mapped it with the same operator (Reinhard). So the differences in the results come only from the curves.<br \/>\n&#8211; <strong>My computer:<\/strong> MacBook Air, Apple M4 chip, 24 GB RAM, Python 3.9, NumPy 2.0, OpenCV 5.0.<\/p>\n<h3>Metrics<\/h3>\n<ul>\n<li><strong>log-RMSE:<\/strong> used when the true curve is known (virtual camera). It is the difference between my curve and the true curve in log scale. Lower is better.<\/li>\n<li><strong>Consistency error (in stops):<\/strong> used for real cameras, because there the true curve is unknown. If the curve is correct, every photo gives the same light value for the same pixel. I measure how much these values change between photos. 1 stop = 2 times. Lower is better.<\/li>\n<\/ul>\n<h2>Problems<\/h2>\n<ol>\n<li><strong>The first code I found did not work.<\/strong> I first tried the VFX_HDR repository from GitHub. It did not run on my computer: first OpenCV was missing, and the code also uses <code>np.warnings<\/code>, which does not exist in new NumPy versions. Its Debevec also uses only 25 pixels, so the curve is not accurate. I decided to use the Piccante library and OpenCV instead.<\/li>\n<li><strong>Setting up Python.<\/strong> The system Python on my Mac did not have OpenCV. I created a virtual environment (<code>python3 -m venv .venv<\/code>) and installed the packages with <code>pip install -r requirements.txt<\/code>.<\/li>\n<li><strong>Wrong EXIF times on the Nikon D2H.<\/strong> See Section 6.2. Because of this, I used &#8220;exposure doubles every photo&#8221; for all course sequences, as the readme says.<\/li>\n<\/ol>\n<h2>Virtual camera (true curve is known)<\/h2>\n<p>The virtual camera images were rendered with the Radiance renderer, and we know their response: <code>srgb<\/code>, <code>linear<\/code>, and <code>noise25\/50\/100\/150<\/code> (sRGB with more and more noise).<\/p>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-10\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1-300x172.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1-1024x588.png 1024w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1-768x441.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1-1536x882.png 1536w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/1_virtual_camera_srgb_curves_1.png 1934w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-11\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/2_virtual_camera_error_vs_srgb-300x195.png\" alt=\"\" width=\"300\" height=\"195\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/2_virtual_camera_error_vs_srgb-300x195.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/2_virtual_camera_error_vs_srgb-768x498.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/2_virtual_camera_error_vs_srgb.png 891w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>log-RMSE<\/th>\n<th>Debevec (OpenCV)<\/th>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>sRGB camera<\/td>\n<td>0.0239<\/td>\n<td><strong>0.0068<\/strong><\/td>\n<td>0.0124<\/td>\n<\/tr>\n<tr>\n<td>linear camera<\/td>\n<td>0.0241<\/td>\n<td>0.0057<\/td>\n<td><strong>0.0027<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul>\n<li><strong>All three versions find the sRGB curve.<\/strong> Piccante&#8217;s Debevec is the closest. OpenCV&#8217;s Debevec is good until about z = 240 and then goes up, because its 70 random samples have very few bright pixels.<\/li>\n<li><strong>For the linear camera, Mitsunaga is the best.<\/strong> A linear curve is exactly a polynomial of order 1, so the model fits it perfectly.<\/li>\n<\/ul>\n<h3>Effect of noise<\/h3>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-12\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves-300x172.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves-1024x588.png 1024w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves-768x441.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves-1536x882.png 1536w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/3_virtual_camera_noise150_curves.png 1932w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>log-RMSE<\/th>\n<th>Debevec (OpenCV)<\/th>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>no noise<\/td>\n<td>0.024<\/td>\n<td><strong>0.007<\/strong><\/td>\n<td>0.012<\/td>\n<\/tr>\n<tr>\n<td>noise 25<\/td>\n<td>0.064<\/td>\n<td><strong>0.010<\/strong><\/td>\n<td>0.019<\/td>\n<\/tr>\n<tr>\n<td>noise 50<\/td>\n<td>0.100<\/td>\n<td><strong>0.019<\/strong><\/td>\n<td>0.092<\/td>\n<\/tr>\n<tr>\n<td>noise 100<\/td>\n<td>0.132<\/td>\n<td><strong>0.045<\/strong><\/td>\n<td>0.543<\/td>\n<\/tr>\n<tr>\n<td>noise 150<\/td>\n<td>0.145<\/td>\n<td><strong>0.070<\/strong><\/td>\n<td>0.626<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Debevec gets worse slowly when the noise increases, because the smoothness term keeps the curve smooth. Mitsunaga breaks after noise 50\u2013100: noisy dark pixels pull the polynomial below zero. (I assume the noise sequences have the same sRGB response. The small errors at low noise support this.)<\/p>\n<h2>Effect of the parameters<\/h2>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-13\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep-300x103.png\" alt=\"\" width=\"300\" height=\"103\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep-300x103.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep-1024x353.png 1024w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep-768x265.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep-1536x530.png 1536w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/4_parameter_sweep.png 1784w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p><strong>Debevec \u2013 \u03bb and number of samples<\/strong><br \/>\n&#8211; Small \u03bb (1) lets the noise into the curve. Big \u03bb (100 or more) makes the curve too flat at the ends. Good values were <strong>\u03bb = 5\u201320<\/strong> for clean data and <strong>\u03bb = 20\u201350<\/strong> for the noisy phone images.<br \/>\n&#8211; More samples helped OpenCV a lot (50 \u2192 200). Piccante was already good with fewer samples, thanks to the histogram sampling.<br \/>\n&#8211; More samples make Debevec slower, because the linear system gets bigger.<\/p>\n<p><strong>Mitsunaga \u2013 polynomial order N<\/strong><br \/>\n&#8211; N = 1\u20132 is too simple and N = 3 was the best. Higher orders (N \u2265 4) gave much worse curves, so the automatic selection between 1 and 3 is a good choice.<br \/>\n&#8211; Estimating the exposure ratios gave almost the same result as using the real ratios. The method really does not need exact exposure times when the data is clean.<\/p>\n<h2>Real cameras<\/h2>\n<h3>\u00a0Sample sequences<\/h3>\n<p>All four sequences were run with &#8220;exposure doubles every photo&#8221; and resized to 1600 pixels.<\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>Consistency error (stops)<\/th>\n<th>Debevec (OpenCV)<\/th>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Canon EOS 550D<\/td>\n<td>0.168<\/td>\n<td><strong>0.167<\/strong><\/td>\n<td>2.288<\/td>\n<\/tr>\n<tr>\n<td>Minolta DiMAGE A1<\/td>\n<td>0.129<\/td>\n<td><strong>0.126<\/strong><\/td>\n<td>2.053<\/td>\n<\/tr>\n<tr>\n<td>Nikon Coolpix E5400<\/td>\n<td>0.095<\/td>\n<td><strong>0.092<\/strong><\/td>\n<td>1.398<\/td>\n<\/tr>\n<tr>\n<td>Nikon D2H<\/td>\n<td>0.110<\/td>\n<td><strong>0.107<\/strong><\/td>\n<td>0.161<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-14\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves-300x172.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves-1024x588.png 1024w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves-768x441.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves-1536x882.png 1536w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/5_canon_550d_curves.png 1932w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<ul>\n<li>The two Debevec versions give almost the same curve for every camera.<\/li>\n<li>Mitsunaga looks fine in the linear plot, but in the log plot it is almost zero below z \u2248 15. So dark pixels get almost no light, and the consistency error becomes very big.<\/li>\n<\/ul>\n<h3>6.2 Nikon D2H: wrong EXIF times<\/h3>\n<p>The EXIF data of the D2H says 1\/400, 1\/320, 1\/200, 1\/160, 1\/100, 1\/80, 1\/40, 1\/20, 1\/10 s. The ratios between them are 1.25 and 1.6, but the images clearly get 2 times brighter every time, as the readme says. With the EXIF times, Debevec&#8217;s error was 0.259 stops; with doubling it was 0.107. So the EXIF times were wrong, and doubling was the right choice.<\/p>\n<h2>7. My phone&#8217;s response curve<\/h2>\n<ul>\n<li><strong>Phone:<\/strong> Samsung Galaxy S20 FE<\/li>\n<li><strong>How I captured:<\/strong>\u00a0 Samsung camera app, Pro mode, JPEG. ISO 500, white balance and focus fixed. I only changed the shutter speed: 1\/30, 1\/20, 1\/10, 1\/6, 1\/4, 1\/2, 1, 2, 4, 10 s (10 photos).<\/li>\n<li><strong>Scene:<\/strong> In the 10 s photo 84% of the pixels are saturated, in the 1\/30 s photo almost none. So all pixel values from 0 to 255 are used.<\/li>\n<\/ul>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-15\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves-300x172.png\" alt=\"\" width=\"300\" height=\"172\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves-300x172.png 300w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves-1024x588.png 1024w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves-768x441.png 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves-1536x882.png 1536w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/6_phone_curves.png 1932w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>Consistency error (stops)<\/th>\n<th>Debevec (OpenCV)<\/th>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Samsung S20 FE<\/td>\n<td>0.129<\/td>\n<td><strong>0.128<\/strong><\/td>\n<td>0.771<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&#8211; The two Debevec versions are almost the same, so I take this as my phone&#8217;s curve.<br \/>\n&#8211; <strong>My phone&#8217;s curve is not sRGB.<\/strong> When I fit a power function, the exponent is about 1.4 (blue), 1.6 (green) and 1.5 (red). For sRGB it is about 2.2. The phone&#8217;s curve is flatter in the middle and bends quickly near 255. I think this is because the phone applies its own tone mapping.<br \/>\n&#8211; <strong>The blue channel is the hardest.<\/strong> The lamp light is orange, so blue values are low and noisy, and the curve has small steps.<br \/>\n&#8211; <strong>Mitsunaga failed on the phone.<\/strong> It chose N = 2, the curve went below zero for dark values, and the HDR image got a purple color.<\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-16\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/7_phone_hdr_debevec-225x300.jpg\" alt=\"\" width=\"225\" height=\"300\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/7_phone_hdr_debevec-225x300.jpg 225w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/7_phone_hdr_debevec-768x1024.jpg 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/7_phone_hdr_debevec-1152x1536.jpg 1152w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/7_phone_hdr_debevec.jpg 1200w\" sizes=\"auto, (max-width: 225px) 100vw, 225px\" \/><\/p>\n<\/td>\n<td>\n<p style=\"background: #fff3cd;padding: 6px;border: 1px dashed #c90\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-17\" src=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/8_phone_hdr_mitsunaga-225x300.jpg\" alt=\"\" width=\"225\" height=\"300\" srcset=\"https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/8_phone_hdr_mitsunaga-225x300.jpg 225w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/8_phone_hdr_mitsunaga-768x1024.jpg 768w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/8_phone_hdr_mitsunaga-1152x1536.jpg 1152w, https:\/\/blog.metu.edu.tr\/e160477\/files\/2026\/11\/8_phone_hdr_mitsunaga.jpg 1200w\" sizes=\"auto, (max-width: 225px) 100vw, 225px\" \/><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>8. Running times<\/h2>\n<p>&nbsp;<\/p>\n<table style=\"border-collapse: collapse\" border=\"1\" cellpadding=\"5\">\n<thead>\n<tr>\n<th>Sequence<\/th>\n<th>Images<\/th>\n<th>Size<\/th>\n<th>Debevec (OpenCV)<\/th>\n<th>Debevec (Piccante)<\/th>\n<th>Mitsunaga (Piccante)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Virtual camera sRGB<\/td>\n<td>9<\/td>\n<td>1024\u00d71024<\/td>\n<td>26 ms<\/td>\n<td>168 ms<\/td>\n<td>69 ms<\/td>\n<\/tr>\n<tr>\n<td>Virtual camera linear<\/td>\n<td>7<\/td>\n<td>1024\u00d71024<\/td>\n<td>24 ms<\/td>\n<td>149 ms<\/td>\n<td>57 ms<\/td>\n<\/tr>\n<tr>\n<td>Virtual camera noise25<\/td>\n<td>9<\/td>\n<td>1024\u00d71024<\/td>\n<td>27 ms<\/td>\n<td>157 ms<\/td>\n<td>59 ms<\/td>\n<\/tr>\n<tr>\n<td>Virtual camera noise50<\/td>\n<td>9<\/td>\n<td>1024\u00d71024<\/td>\n<td>26 ms<\/td>\n<td>152 ms<\/td>\n<td>54 ms<\/td>\n<\/tr>\n<tr>\n<td>Virtual camera noise100<\/td>\n<td>9<\/td>\n<td>1024\u00d71024<\/td>\n<td>27 ms<\/td>\n<td>169 ms<\/td>\n<td>52 ms<\/td>\n<\/tr>\n<tr>\n<td>Virtual camera noise150<\/td>\n<td>9<\/td>\n<td>1024\u00d71024<\/td>\n<td>27 ms<\/td>\n<td>155 ms<\/td>\n<td>53 ms<\/td>\n<\/tr>\n<tr>\n<td>Canon EOS 550D<\/td>\n<td>9<\/td>\n<td>1600\u00d71067<\/td>\n<td>28 ms<\/td>\n<td>213 ms<\/td>\n<td>113 ms<\/td>\n<\/tr>\n<tr>\n<td>Minolta DiMAGE A1<\/td>\n<td>13<\/td>\n<td>1600\u00d71200<\/td>\n<td>28 ms<\/td>\n<td>304 ms<\/td>\n<td>175 ms<\/td>\n<\/tr>\n<tr>\n<td>Nikon Coolpix E5400<\/td>\n<td>16<\/td>\n<td>1600\u00d71200<\/td>\n<td>31 ms<\/td>\n<td>389 ms<\/td>\n<td>244 ms<\/td>\n<\/tr>\n<tr>\n<td>Nikon D2H<\/td>\n<td>9<\/td>\n<td>1600\u00d71060<\/td>\n<td>27 ms<\/td>\n<td>206 ms<\/td>\n<td>108 ms<\/td>\n<\/tr>\n<tr>\n<td>Samsung S20 FE (my phone)<\/td>\n<td>10<\/td>\n<td>1200\u00d71600<\/td>\n<td>29 ms<\/td>\n<td>244 ms<\/td>\n<td>145 ms<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Times are the median of 3 runs on a MacBook Air (Apple M4, 24 GB RAM), only for finding the curve.<br \/>\n&#8211; <strong>Debevec (OpenCV)<\/strong> is fast because it uses only 70 samples.<br \/>\n&#8211; <strong>Debevec (Piccante)<\/strong> is the slowest: 256 samples make a big linear system.<br \/>\n&#8211; <strong>Mitsunaga<\/strong> solves a very small system. Most of its time goes to making the histograms, so it gets slower with more and bigger images.<\/p>\n<h2>9. Conclusion<\/h2>\n<ul>\n<li>With clean data, both algorithms find the true curve. Mitsunaga is the best for the linear camera and it is fast.<\/li>\n<li><strong>Noise makes the difference.<\/strong> Debevec stays good with noise. Mitsunaga breaks with medium and high noise.<\/li>\n<li>On all real cameras and my phone, <strong>Debevec was the most reliable.<\/strong> One polynomial cannot follow the dark part of real camera curves well.<\/li>\n<li><strong>Sample selection matters.<\/strong> OpenCV and Piccante solve the same Debevec equations, but the results are a bit different because of the samples.<\/li>\n<li>EXIF times can be wrong (Nikon D2H).<\/li>\n<li>My Samsung S20 FE does not have an sRGB curve. Its curve is flatter, with an exponent of about 1.5.<\/li>\n<\/ul>\n<p><strong>Use of AI:<\/strong> I used an AI assistant (Claude) to convert the Piccante code to Python, help of AI is used for\u00a0 writing the comparison scripts I took the phone photos, ran all experiments on my own computer, checked the results and edited the post.<\/p>\n<h2>References<\/h2>\n<ol>\n<li>P. Debevec, J. Malik, &#8220;Recovering High Dynamic Range Radiance Maps from Photographs&#8221;, SIGGRAPH 1997.<\/li>\n<li>T. Mitsunaga, S. K. Nayar, &#8220;Radiometric Self Calibration&#8221;, CVPR 1999.<\/li>\n<li>F. Banterle et al., Piccante HDR library, https:\/\/github.com\/banterle\/piccante<\/li>\n<li>OpenCV documentation, https:\/\/docs.opencv.org<\/li>\n<li>M. Grossberg, S. Nayar, &#8220;What can be known about the radiometric response from images?&#8221;, ECCV 2002.<\/li>\n<li>E. Reinhard et al., &#8220;Photographic Tone Reproduction for Digital Images&#8221;, SIGGRAPH 2002.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>This work is about comparing two algorithms of generating HDR image. To generate HDR image different exposed images are combined. A camera creates images by converting real light and exposure time into digital level via non linear function which is called the camera response function (CRF): Z = f(E \u00b7 \u0394t) Then to form HDR [&hellip;]<\/p>\n","protected":false},"author":9678,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[1],"tags":[],"class_list":["post-9","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/posts\/9","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/users\/9678"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/comments?post=9"}],"version-history":[{"count":0,"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/posts\/9\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/media?parent=9"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/categories?post=9"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e160477\/wp-json\/wp\/v2\/tags?post=9"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}