Enhance shadows and brightness #4036
Replies: 15 comments 19 replies
-
Small correction: vips_tonemap() was misspelled, but is vips_tonelut(). |
Beta Was this translation helpful? Give feedback.
-
Hello @CoachRDeveloper, Usually you'd transform to CIELAB, tonemap the L, and transform back to RGB. nip2 will to do this automatically I think. Perhaps: But of course it depends on your needs. The libvips tonemap operation was originally designed for prepress work. Generally you'd hire a good photographer and they'll do all this for you in lightroom or the phase one thing. |
Beta Was this translation helpful? Give feedback.
-
Many thanks for reply and providing the information. As taking photos is automated and cameras preinstalled, hiring a good photographer is not realistic. Usually it is lack of willingness to invest in enough LED tubes or changes made by the owner of the studio to e.g. the floor (e.g. switching to a black floor) without adjusting the required amount of lights. Therefore, we have to rely on some tricks to enhance the photo quality to some extend. From what I tried myself (looks much like you did with tonelut), I noticed that the red color became less vibrant when enhancing the shadows. But which operation did you apply to enhance the photo? I have to do it programmatically in C++. Please forgive me my lack of knowledge about when to apply enhancing brightness, tone curve or HDR (with the use of libvips, as our code already is using libvips). |
Beta Was this translation helpful? Give feedback.
-
I forgot something: is there something as 'a generic tone mapping' curve (=LUT table) that can be applied to any car (under same lightness conditions, taken from same location), even if the car color is different (which can be any color)? And how to address aging of the LED tubes (i.e. reduced light output).? |
Beta Was this translation helpful? Give feedback.
-
The tone map menu item in nip2 doesn't change the saturation or hue, just the brightness. If the colours seem less vibrant, it may be your display. Accurate colour matching is a painful and expensive business -- you need a high-quality display, you need to generate a custom display profile, you need to standardise viewing conditions in the room (ie. paint the walls with a good-quality neutral 50% grey paint, use high-quality lighting, exclude daylight, wait for 10 minutes after entering the room and before starting work, have a viewing cabinet for the match item, etc.). I spent years doing this :( Yes,
https://www.libvips.org/API/current/libvips-create.html#vips-tonelut |
Beta Was this translation helpful? Give feedback.
-
It is somewhat unclear to me what the difference is between VIPS_INTERPRETATION_LABS and VIPS_INTERPRETATION_LAB. I guess the latter is 8-bit, although not explicitly mentioned on https://www.[libvips](https://www.libvips.org/API/current/VipsImage.html).org/API/current/VipsImage.html, but for VIPS_INTERPRETATION_LABS, I don't need to set in-max/in-min, as the default (short int values) seem to suffice. |
Beta Was this translation helpful? Give feedback.
-
LABS is 16-bit (ie. short) CIELAB, LABQ is a strange 8 bit CIELAB with some extra bits in the fourth byte. LAB is anything you like, but pixels need to be 0-100 for L and +/-128 for AB. https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation I played about with your image a little more and I think the main problem is the underexposure. I would go to XYZ first and scale everything up by maybe a factor of 2.5: If you need to lift the tyres a bit more than that, you could just boost the shadows: |
Beta Was this translation helpful? Give feedback.
-
I made a tiny demo prog for you: /* compile with
* g++ -g -Wall tonemap.cpp `pkg-config vips-cpp --cflags --libs`
*/
#include <vips/vips8>
using vips::VImage;
int
main(int argc, char **argv)
{
if (VIPS_INIT(""))
vips_error_exit(nullptr);
auto image = VImage::new_from_file(argv[1]);
// import using the embedded profile, and import to XYZ PCS
image = image.icc_import(VImage::option()
->set("embedded", TRUE)
->set("pcs", "xyz"));
// brightness boost ... this is best done in XYZ space, where pixel values
// are proportional to the number of photons
//
// 2.5 seems about right for the sample image (bad underexposure)
image *= 2.5;
// to labq, then labs .. this route clips out of range values
// (with 8.16 you can go straight to labs)
auto labs = image
.colourspace(VIPS_INTERPRETATION_LABQ)
.colourspace(VIPS_INTERPRETATION_LABS);
auto l = labs[0];
auto ab = labs.extract_band(1, VImage::option()->set("n", 2));
// L will be ushort with L* scaled to fill 0-32767
//
// take the histogram and find the 1% and 99% points, then set that as the
// range ... we want to avoid specular highlights and any pools of 0s
auto black = l.percent(1.0);
auto white = l.percent(99.0);
printf("black = %d\n", black);
printf("white = %d\n", white);
// make a tone curve for this image
auto tone = VImage::tonelut(VImage::option()
// tonal range
->set("Lb", 100.0 * black / 32767.0)
->set("Lw", 100.0 * white / 32767.0)
// position of shadow, midtone and highlight in 0-1
->set("Ps", 0.15)
->set("Pm", 0.5)
->set("Ph", 0.8)
// shadow, midtone and highlight adjust, +/- 30
//
// shadows +10 seems to help the tyres
->set("S", 10.0)
->set("M", 0.0)
->set("H", 0.0)
);
// use the tonecurve to adjust L*
l = l.maplut(tone);
// rebuild the labs image, then back to srgb
image = l
.bandjoin(ab)
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LABS))
.colourspace(VIPS_INTERPRETATION_sRGB);
image.write_to_file(argv[2]);
return 0;
} It makes: |
Beta Was this translation helpful? Give feedback.
-
Wow, many thanks. And tricks like using LABQ before moving to LABS, I could never have invented this myself. I will try your code today. Again, thanks for great help. The color temperature is typically determined by the camera, when adjusting the white balance upon first shoot in the car studio. Q1: Q2: The documentation https://www.[libvips](https://www.libvips.org/API/current/libvips-arithmetic.html#vips-stats).org/API/current/libvips-arithmetic.html#vips-stats says that row 0 is for all bands, and then 1 and so on per band. However, in the getter https://www.libvips.org/API/8.11/cpp/classVImage.html, search for operator(), then it is not described wether x points to rows or columns. It seems to be rows. Can you please confirm? I have checked this, because the following lines return the same value, which is hopefully a proof of the previous conclusion:
Q3: Is there a possibility to let show/trace NIP2 the executed VIPS commands? I guess that vips.exe is being called and with tracing, I would have more information to deduce the required C++ actions (like you now did for me, but I don't like to bother you each time).... Q4: I just discovered that 8.16 is not yet released. I'm at 8.15.1 (just one version behind last released one), on Windows. When will 8.16 get released? |
Beta Was this translation helpful? Give feedback.
-
I know the problem is underexposure. But the owners of the car studio have their own reasons for not upgrading their studio with additional LED tubes. That's why we have to apply some 'tricks' to provide better quality, as underexposed photos are not an advertisement for us. So we have to do something, but still need to convince the studio owner that uprading the exposure conditions are the better approach (which is not easy). |
Beta Was this translation helpful? Give feedback.
-
I've tried out your suggestions and the result is very good. The biggest enhancement is in adjusting the brightness and tonemapping enhances the details in the tyres. Thanks again. |
Beta Was this translation helpful? Give feedback.
-
Underexposure is best fixed by multiplying by a constant in XYZ space. Underexposure is a lack of photons (obviously), XYZ space has numeric values proportional to the number of photons, so just scaling up is a very easy fix. You generally want to get the brightest diffuse reflector in the scene (the left-hand back wall in this case) up to about Y 98. The specular highlights on the car (eg. paint, chrome) can go quite a bit higher than this (they reflect the lights directly back into the camera), so you need to be a little careful what you set exposure off. If they are happy with the colour balance, then of course leave it, but you would generally aim for the media white point, which is 250, 250, 250 for JPEG, and they are some way off that. |
Beta Was this translation helpful? Give feedback.
-
Yes, (x, y) is always (columns, rows), like in maths. You can right-click on an image in nip2 and select History to get a list of the commands that generated that image. It's using the vips7 API though, so translating to vips8 isn't always easy. I'm working on a vips8 update for nip2 (will be nip4 heh) at the moment: https://github.com/jcupitt/nip4 8.16 will be some time late this summer, perhaps August or September. |
Beta Was this translation helpful? Give feedback.
-
One small addition: Because we are typically reading the images from a SMB share, I have played around with reading in sequential mode: However, then the above example crashes at the end, when dereferencing memory (on Windows, using libvips 8.15.1). In our code, the error is slightly different: adjusting the brightness worked fine, i.e. after the line: I was able to save the file, but when executing: then I got the following warnings + error:
The latter was caused by an exception. I don't understand why I got in our code another error, as that looks identical to your code (but using our own wrapper class and put adjust brightness and change lumininance in two distinct methods). I was not able to reproduce this now in a smaller example. Also, the crash in the small example was also not reproducible in our own code (unfortunatelly). When I use the default open mode, then no issues happened. Also VIPS_ACCESS_SEQUENTIAL_UNBUFFERED causes the same error. But the final question (and the reason why I tried this): does SMB reads (whith a NAS behind, with traditinal HDDs) benefit from using libvips sequential reads (of course, at the price of using more memory)? |
Beta Was this translation helpful? Give feedback.
-
In addition to my previous question today (see above) about 8.16, some minor clarification and a reference to the piece of code supplied by you that triggers this question: Does 8.16 indeed solve the issue mentioned in this code piece (see also above for the whole part/contect)
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I've played around with vips_tonelut and vips_maplut, but it does not have the results wanted. What we like to achieve is:
I've added an example image and played around with NIP2, but did not get satisfying results (even adjust tone curve comes with somewhat disappointing result). When needed, I can add that result as well, but as I'm not experienced with image (light, shadow,) enhancements, I may have performed the wrong actions (for now: just plain adjust tone mapping on a sRGB image).

I also discovered that adjusting the tone curve, and setting shadow adjust to e.g. 7, the red color get less saturized (i.e. the color of the car changes from the real color). In ideal case, the colors should remain (almost) the same. Is it possible to adjust shadows/brightness without losing color saturation?
Please let me know your recommendations.
BTW we are already using libvips for thumbnails, resizes, crops, sharpening etc.
Beta Was this translation helpful? Give feedback.
All reactions