0

I trying to reduce my image colors to some predefined colors using the following function:

void quantize_img(cv::Mat &lab_img, std::vector<cv::Scalar> &lab_colors) {
    float min_dist, dist;
    int min_idx;
    for (int i = 0; i < lab_img.rows*lab_img.cols * 3; i += lab_img.cols * 3) {
        for (int j = 0; j < lab_img.cols * 3; j += 3) {
            min_dist = FLT_MAX;
            uchar &l = *(lab_img.data + i + j + 0);
            uchar &a = *(lab_img.data + i + j + 1);
            uchar &b = *(lab_img.data + i + j + 2);
            for (int k = 0; k < lab_colors.size(); k++) {
                double &lc = lab_colors[k](0);
                double &ac = lab_colors[k](1);
                double &bc = lab_colors[k](2);
                dist = (l - lc)*(l - lc)+(a - ac)*(a - ac)+(b - bc)*(b - bc);
                if (min_dist > dist) {
                    min_dist = dist;
                    min_idx = k;
                }
            }
            l = lab_colors[min_idx](0);
            a = lab_colors[min_idx](1);
            b = lab_colors[min_idx](2);
        }
    }
}

However it does not seem to work properly! For example the output for the following input looks amazing!

if (!(src = imread("im0.png")).data)
    return -1;
cvtColor(src, lab, COLOR_BGR2Lab);
std::vector<cv::Scalar> lab_color_plate_({
    Scalar(100,  0 ,   0),  //white
    Scalar(50 ,  0 ,   0),  //gray
    Scalar(0  ,  0 ,   0),  //black
    Scalar(50 , 127, 127),  //red
    Scalar(50 ,-128, 127),  //green
    Scalar(50 , 127,-128),  //violet
    Scalar(50 ,-128,-128),  //blue
    Scalar(68 , 46 ,  75),  //orange
    Scalar(100,-16 ,  93)   //yellow
});
//convert from conventional Lab to OpenCV Lab
for (int k = 0; k < lab_color_plate_.size(); k++) {
    lab_color_plate_[k](0) *= 255.0 / 100.0;
    lab_color_plate_[k](1) += 128;
    lab_color_plate_[k](2) += 128;
}
quantize_img(lab, lab_color_plate_);
cvtColor(lab, lab, CV_Lab2BGR);
imwrite("im0_lab.png", lab);

Input image: Input image

Output image enter image description here

Can anyone explain where the problem is?

3
  • I do not get why for (int j = 0; j < lab_img.cols * 3; j += 3) if you are already iterating over all pixels (the for with i) if you need the j just do j = i%(lab_img.cols*3). In a little more less confusing way I would have taken it as a vec3b array (with reinterpret_cast) and used std::transform with a lambda that returns the new color. Anyways, I think if you remove the j for loop, and the j from the l,a,b indeces you will get it right :) (i goes already pixel (of 3 channels) per pixel in the whole image)
    – api55
    Commented Oct 26, 2017 at 14:30
  • @api55 Actually "i" steps over rows (note i+= lab_img.cols*3) and "j" steps over cols (note j+=3). So the loop seems correct. Of course, your tips are definitely useful for improving the code. Thanks Commented Oct 26, 2017 at 21:37
  • true,i got confused. I do not see any mistake right now, i will try to run it and se. For me the result seems to colors that all the numbers are positive at the beginning (except red), but your conversion to opencv convention seems ok.... not sure now why
    – api55
    Commented Oct 27, 2017 at 4:51

1 Answer 1

1

After checking your algorithm I noticed that the algorithm is correct 100% and the problem is your color space.... Let's take one of the colors that is changed "wrongly" like the green from the trees.

Using a color picker tool in GIMP it tells you that at least one of the green used is in RGB (111, 139, 80). When this is converted to LAB, you get (54.4, -20.7, 28.3). The distance to green is (by your formula) 21274.34 , and with grey the distance is 1248.74... so it will choose grey over green, even though it is a green color.

A lot of values in LAB can generate a green value. You can test it out the color ranges in this webpage. I would suggest you to use HSV or HSL and compare the H values only which is the Hue. The other values changes only the tone of green, but a small range in the Hue determines that it is green. This will probably give you more accurate results.

As some suggestion to improve your code, use Vec3b and cv::Mat functions like this:

for (int i = 0; i < lab_img.rows; ++i) {
    for (int j = 0; j < lab_img.cols; ++j) {
        Vec3b pixel = lab_img.at<Vec3b>(i,j);
    }
}

This way the code is more readable, and some checks are done in debug mode.

The other way would be to do a one loop since you don't care about indices

 auto currentData = reinterpret_cast<Vec3b*>(lab_img.data); 
 for (size_t i = 0; i < lab_img.rows*lab_img.cols; i++)
 {
     auto& pixel = currentData[i];
 }

This way is also better. This last part is just a suggestion, there is nothing wrong with your current code, just harder to read understand to the outside viewer.

2
  • The main reason that I preferred to use Lab color-space is described on Color Difference Wiki, but, in a nutshell, RGB distance is not "perceptually uniform". The article suggested to use Lab color space and I simply use CIE76 DeltaE. However, let me try your suggestion. Commented Oct 27, 2017 at 9:14
  • Yep, RGB is bad for this finding the difference. However I think the article talks about how the human perceives it, and that less than 2.3 is not noticeable for humans... but you are interested is that light green and dark green are green, right? in Lab it is more like, dark blue is perceptually similar to dark green and then it gives wrong answer (at least that is how I see it). But since you want to do is quantization of colors, you need is to find the colors that are similar
    – api55
    Commented Oct 27, 2017 at 9:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.