Click here to Skip to main content
15,867,686 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I've been using a color matching algorithm I created based on the information here:

Nearest color algorithm using Hex Triplet - Software Engineering Stack Exchange[^]

The trouble is, it's not quite giving me all the information I think.

I need to compare two - say RGB pixels

So like

C++
constexpr double difference(type rhs) const {
    return helpers::pixel_diff_impl<type,0,ChannelTraits...>::diff_sum(*this,rhs);
}


The goofy diff_sum() calls just add the squared value of each channel difference together. so like (lhs[R]-rhs[R])2+(lhs[G]-rhs[G])2+(lhs[B]-rhs[B])2

This isn't yielding what I want. It works on my 7-color e-paper display for the most part, properly matching red, blue, yellow and green so far, but is stuck on orange. Orange is not matching and I can't figure out why. It comes up as red.


I also have some accompanying code that uses the difference code. I've put it in what I have tried.

Disclaimer: I realize some of this code may take explaining. The trouble is I'm not sure which parts need the most explanation. It's too familiar to me, if that makes sense. Leave comments with your questions, and I'll do my best to answer them.

What I have tried:

Here are a couple of templates I use to get difference values.
The first one will basically do (R-R2)2+(G-G2)2+(B-B2)2

The last one will basically do (R2+G2+B2)-(R22+G22+B22)

I DO NOT USE the last one. It was one I was simply trying.

C++
template<typename PixelType,int Count,typename... ChannelTraits>
struct pixel_diff_impl;        
template<typename PixelType,int Count, typename ChannelTrait,typename... ChannelTraits>
struct pixel_diff_impl<PixelType,Count,ChannelTrait,ChannelTraits...> {
    using ch = typename PixelType::template channel_by_index<Count>;
    using next = pixel_diff_impl<PixelType,Count+1, ChannelTraits...>;
    constexpr static inline double diff_sum(PixelType lhs,PixelType rhs) {
        constexpr const size_t index = Count;
        if(ChannelTrait::bit_depth==0) return NAN;
        const double d = (lhs.template channelr<index>()-rhs.template channelr<index>());
        return d*d+next::diff_sum(lhs,rhs);
    }
};
template<typename PixelType,int Count>
struct pixel_diff_impl<PixelType,Count> {
    constexpr static inline double diff_sum(PixelType lhs,PixelType rhs) {
        return 0.0;
    }
    
};

template<typename PixelType,int Count,typename... ChannelTraits>
struct pixel_sumsqr_impl;        
template<typename PixelType,int Count, typename ChannelTrait,typename... ChannelTraits>
struct pixel_sumsqr_impl<PixelType,Count,ChannelTrait,ChannelTraits...> {
    using ch = typename PixelType::template channel_by_index<Count>;
    using next = pixel_sumsqr_impl<PixelType,Count+1, ChannelTraits...>;
    constexpr static inline double sumsqr(PixelType lhs) {
        constexpr const size_t index = Count;
        if(ChannelTrait::bit_depth==0) return NAN;
        const double d = lhs.template channel<index>();
        return d*d+next::sumsqr(lhs);
    }
};
template<typename PixelType,int Count>
struct pixel_sumsqr_impl<PixelType,Count> {
    constexpr static inline double sumsqr(PixelType lhs) {
        return 0.0;
    }
    
};


Here's the difference code:
C++
constexpr double difference(type rhs) const {
    return helpers::pixel_diff_impl<type,0,ChannelTraits...>::diff_sum(*this,rhs);
}


Here's the code that uses difference.
C++
template<typename PixelType>
struct waveshare5in65_palette {
private:
    constexpr static gfx::gfx_result index_to_mapped(int idx,PixelType* result) {
        switch(idx) {
            case 0:
                return gfx::convert(gfx::rgb_pixel<16>(0,0,0),result);
            case 1:
                return gfx::convert(gfx::rgb_pixel<16>(31,63,31),result);
            case 2:
                return gfx::convert(gfx::rgb_pixel<16>(0,63,0),result);
            case 3:
                return gfx::convert(gfx::rgb_pixel<16>(0,0,31),result);
            case 4:
                return gfx::convert(gfx::rgb_pixel<16>(31,0,0),result);
            case 5:
                return gfx::convert(gfx::rgb_pixel<16>(31,63,0),result);
            default: //case 6:
                return gfx::convert(gfx::rgb_pixel<16>(31,31,0),result);
        }
    }
public:
    using type = waveshare5in65_palette;
    using pixel_type = gfx::pixel<gfx::channel_traits<gfx::channel_name::index,4,0,6>>;
    using mapped_pixel_type = PixelType;
    constexpr static const bool writable = false;
    constexpr static const size_t size = 6;
    gfx::gfx_result map(pixel_type pixel,mapped_pixel_type* mapped_pixel) const {
        return index_to_mapped(pixel.channel<gfx::channel_name::index>(),mapped_pixel);
    }
    //
    // This is the critical matching code I need to work
    //
    gfx::gfx_result nearest(mapped_pixel_type mapped_pixel,pixel_type* pixel) const {
        
        if(nullptr==pixel) {
            return gfx::gfx_result::invalid_argument;
        }
        mapped_pixel_type mpx;
        gfx::gfx_result r = index_to_mapped(0,&mpx);
        if(gfx::gfx_result::success!=r) {
            return r;
        }
        double least = mpx.difference(mapped_pixel);
        if(0.0==least) {
            pixel->native_value = 0;
            return gfx::gfx_result::success;
        }
        int ii=0;
        for(int i = 1;i<size;++i) {
            r=index_to_mapped(i,&mpx);
            if(gfx::gfx_result::success!=r) {
                return r;
            }
            double cmp = mpx.difference(mapped_pixel);
            if(0.0==cmp) {
                ii=i;
                least = 0.0;
                break;
            }
            if(cmp<least) {
                least = cmp;
                ii=i;
            }
        }
        pixel->channel<gfx::channel_name::index>(ii);
        return gfx::gfx_result::success;
    }
};
Posted
Updated 8-Mar-22 11:36am
v7
Comments
CPallini 8-Mar-22 14:43pm    
I possibly don't get you (I surely don't get your code), anyway the Wikipedia page uses
Euclidean distance which is quite different from 'squaring each component of the first pixel, then squaring each component of the second pixel and eventually subract the squared components'. In order to compute the Euclidean distance you have to square the component differences of the two pixels.
honey the codewitch 8-Mar-22 14:47pm    
the first method is a variant of the Euclidian distance method. The final sqrt is removed as it's not necessary in this case.

... and as I was typing this I realized I was doing it wrong above compared to the wiki.

so thank you for probably solving my issue, albeit just by making me check my work.
honey the codewitch 8-Mar-22 15:19pm    
And I did check it. Turns out the formula in my question is wrong, but in the code it isn't. I use a variant of the Euclidian method sans the final sqrt() since I don't need it (i'm only comparing relative distances so if i sqrt none of them it's the same as sqrting all of them). I've since updated the question with the proper formula. thanks for catching that. unfortunately it doesn't fix my code. =/
CPallini 8-Mar-22 16:12pm    
So, now the question is: does your code compute correctly the Euclidean distance and you are unhappy with that or, as opposite, the code doesn't work?
honey the codewitch 8-Mar-22 16:27pm    
I'm working on figuring that out. I had to take a cat nap =)

Edit: I've edited my question significantly to update where I'm at.

Edit 2: There was nothing wrong with my code to begin with. It was an unrelated bug.

Looking at the Wikipedia article on Color difference[^] it seems to me that the formula is
difference = sqrt( (r2-r1)^2 + (g2-g1)^2 + (b2-b1)^2)
As I understand it, you are calculating
difference = sqrt(r2^2 + g2^2 + b1^2) - sqrt(r1^2 + g2^2 + b2^2)
For example:
C++
#include <iostream>
#include <time.h>
#include <cstdlib>
#include <cmath>
#include <fmt/format.h>

double v1(int r1, int r2, int g1, int g2, int b1, int b2)
{
    return sqrt( (r1*r1) + (g1*g1) + (b1*b1) ) - sqrt((r2*r2) + (g2*g2) + (b2*b2));
}

double v2(int r1, int r2, int g1, int g2, int b1, int b2)
{
    int r = r2 - r1;
    int g = g2 - g1;
    int b = b2 - b1;

    return sqrt( (r*r) + (g*g) + (b*b));
}

int main()
{
    srand(time(0));

    for(size_t i = 0; i < 10; ++i)
    {
        int r1 = rand() % 256;
        int g1 = rand() % 256;
        int b1 = rand() % 256;

        int r2 = rand() % 256;
        int g2 = rand() % 256;
        int b2 = rand() % 256;


        fmt::print("[{} {} {}] [{} {} {}] {:.2f} {:.2f}\n", 
            r1, g1, b1, r2, b2, g2,
            v1(r1, r2, b1, b2, g1, g2), v2(r1, r2, b1, b2, g1, g2));

    }

    return 0;
}
might produce output like this:
[115 255 183] [188 30 235] 31.84 170.70
[182 198 116] [120 32 86] 141.83 153.11
[177 209 232] [211 239 21] 39.43 191.18
[93 33 212] [253 171 4] -71.55 167.70
[5 221 23] [114 0 64] 91.51 192.51
[149 179 255] [76 234 111] 75.44 101.95
[106 38 176] [223 7 158] -64.45 238.01
[255 79 216] [231 237 34] 10.70 55.15
[214 127 14] [170 18 124] 38.05 44.28
[86 129 239] [109 47 243] 14.44 224.47
 
Share this answer
 
Comments
honey the codewitch 8-Mar-22 17:17pm    
Earlier there was an error in my question. I tried two different ways, in the question, and have settled on this
(R-R2)2+(G-G2)2+(B-B2)2

See the edit to the question. The code was right, but my description of what i was doing was wrong in the question before I edited it. Sorry.

Edit: It turns out it was an unrelated bug. My color matching code worked fine.
My color matching code worked fine! There was a bug in my palette, where size = 6 instead of size = 7.

Must have been a typo. There are 7 colors in this palette.

That saves me a lot of headache. I really thought my color matching code was bad all this time.

Sorry for the churn!
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900