{"id":4217,"date":"2020-10-30T13:53:18","date_gmt":"2020-10-30T17:53:18","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=4217"},"modified":"2020-10-30T13:53:18","modified_gmt":"2020-10-30T17:53:18","slug":"how-to-compute-perceptual-color-difference","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2020\/10\/30\/how-to-compute-perceptual-color-difference\/","title":{"rendered":"How to Compute Perceptual Color Difference"},"content":{"rendered":"<div class=\"content\"><p>I <a href=\"https:\/\/blogs.mathworks.com\/steve\/2020\/09\/30\/how-to-detect-an-x-rite-colorchecker-chart\/\">wrote previously<\/a> about the new <tt>colorChecker<\/tt>, which can detect X-Rite test charts in the R2020b release. Another area of new color-related functionality is computing perceptual color differences. There is the new function <tt>deltaE<\/tt>, which computes color differences based on the L*a*b* color space and the CIE76 standard. There is also the <tt>imcolordiff<\/tt> function, which can compute color differences based either on the CIE94 or the CIEDE2000 standard.<\/p><p>I recently mentioned to someone on the Image Processing Toolbox team that I was planning to write a blog post about these functions, and he posed the following question: which two colors are the furthest apart, perceptually speaking? I decided to give that a try using $\\Delta_E$. I'll formulate the problem this way: which two sRGB colors have the largest $\\Delta_E$ between them?<\/p><p>In a related post back in 2015, I demonstrated how to plot the sRGB gamut surface in L*a*b* space, like this:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2015\/srgb_gamut_surface_08.png\" alt=\"\"> <\/p><p>Looking at this diagram, you might be able to guess the answer. But, let's work through it. The first thing I want to do is to sample the sRGB space.<\/p><pre class=\"codeinput\">[r,g,b] = ndgrid(linspace(0,1,100));\r\nrgb = [r(:), g(:), b(:)];\r\n<\/pre><p>Next, convert the sRGB colors to Lab. (I'm going to stop typing the \"*\" characters.)<\/p><pre class=\"codeinput\">lab = rgb2lab(rgb);\r\n<\/pre><p>Now let's find the colors on the convex hull of the Lab values. The pair of colors most distant from each other must both be on the convex hull.<\/p><pre class=\"codeinput\">k = convhull(lab);\r\nhull_colors = lab(unique(k(:,1)),:);\r\n<\/pre><p>If this were a two-dimensional problem, then I know of an algorithm that can find the most distant pair of points in $O(N)$, where $N$ is the number of points on the convex hull. I don't know of a similar algorithm in three dimensions, so I'm just going to brute force it by computing the distance between every color pair.<\/p><p>So, how many colors are we talking about?<\/p><pre class=\"codeinput\">size(hull_colors)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n        3980           3\r\n\r\n<\/pre><p>There are 3908 colors, each of which is represented by a 3-element vector in Lab space. The color difference $\\Delta_E$ is the Euclidean distance between two colors in Lab space. I want to compute the distance between every color in that set and every other color. Here is a nifty and compact way to do that using the implicit expansion behavior of MATLAB arithmetic operators.<\/p><pre class=\"codeinput\">C1 = reshape(hull_colors,[],1,3);\r\nC2 = reshape(hull_colors,1,[],3);\r\nCdiff = C1 - C2;\r\nDE = sqrt(sum(Cdiff.^2,3));\r\nsize(DE)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n        3980        3980\r\n\r\n<\/pre><p>You can see that <tt>DE<\/tt> is an 3980x3980 matrix. It contains the pair-wise distances between all the convex hull colors. (See also <tt>pdist2<\/tt>, a function in the Statistics and Machine Learning Toolbox that can compute pair-wise vector distances with much more flexibility, as well as <tt>hypot<\/tt>, a MATLAB function that computes the magnitude of a two-element vector in a way that avoids numerical overflow and underflow problems.)<\/p><p>Let's find where the maximum distances are in this matrix.<\/p><pre class=\"codeinput\">[m,n] = find(DE == max(DE(:)))\r\n<\/pre><pre class=\"codeoutput\">\r\nm =\r\n\r\n        3883\r\n          92\r\n\r\n\r\nn =\r\n\r\n          92\r\n        3883\r\n\r\n<\/pre><p>What are those colors (in Lab space)?<\/p><pre class=\"codeinput\">max_colors_lab = hull_colors(m,:)\r\n<\/pre><pre class=\"codeoutput\">\r\nmax_colors_lab =\r\n\r\n   32.2970   79.1875 -107.8602\r\n   87.7347  -86.1827   83.1793\r\n\r\n<\/pre><p>And those two colors in sRGB space:<\/p><pre class=\"codeinput\">max_colors_rgb = lab2rgb(max_colors_lab)\r\n<\/pre><pre class=\"codeoutput\">\r\nmax_colors_rgb =\r\n\r\n    0.0000   -0.0000    1.0000\r\n    0.0000    1.0000    0.0000\r\n\r\n<\/pre><p>They are just the sRGB green and blue primaries.<\/p><pre class=\"codeinput\">colorSwatches(max_colors_rgb)\r\naxis <span class=\"string\">equal<\/span>\r\naxis <span class=\"string\">off<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/largest_color_difference_01.png\" alt=\"\"> <p>(<tt>colorSwatches<\/tt> is a <a href=\"http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\"><b>DIPUM3E<\/b><\/a> function that is available to you in <i>MATLAB Color Tools<\/i> on the <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/64161-matlab-color-tools\">File Exchange<\/a> and on <a href=\"https:\/\/github.com\/mathworks\/matlab-color-tools\">GitHub<\/a>.)<\/p><p>These two colors have a $\\Delta_E$ of:<\/p><pre class=\"codeinput\">DE(m(1),n(1))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  258.6827\r\n\r\n<\/pre><p>The deltaE function offers a more convenient way to compute $\\Delta_E$:<\/p><pre class=\"codeinput\">deltaE([0 1 0],[0 0 1])\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  258.6827\r\n\r\n<\/pre><p>Some other time, I will write about the other color difference calculations provided by <tt>imcolordiff<\/tt>.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_636a8c37421348b1a0e15a865739af61() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='636a8c37421348b1a0e15a865739af61 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 636a8c37421348b1a0e15a865739af61';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2020 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_636a8c37421348b1a0e15a865739af61()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2020b<br><\/p><\/div><!--\r\n636a8c37421348b1a0e15a865739af61 ##### SOURCE BEGIN #####\r\n%% How to Compute Perceptual Color Difference\r\n% I\r\n% <https:\/\/blogs.mathworks.com\/steve\/2020\/09\/30\/how-to-detect-an-x-rite-colorchecker-chart\/\r\n% wrote previously> about the new |colorChecker|, which can detect\r\n% X-Rite test charts in the R2020b release. Another area of new\r\n% color-related functionality is computing perceptual color differences.\r\n% There is the new function |deltaE|, which computes color differences\r\n% based on the L*a*b* color space and the CIE76 standard. There is also\r\n% the |imcolordiff| function, which can compute color differences based\r\n% either on the CIE94 or the CIEDE2000 standard.\r\n%\r\n% I recently mentioned to someone on the Image Processing Toolbox team that\r\n% I was planning to write a blog post about these functions, and he posed\r\n% the following question: which two colors are the furthest apart,\r\n% perceptually speaking? I decided to give that a try using $\\Delta_E$.\r\n% I'll formulate the problem this way: which two sRGB colors have the\r\n% largest $\\Delta_E$ between them?\r\n%\r\n% In a related post back in 2015, I demonstrated how to plot the sRGB gamut\r\n% surface in L*a*b* space, like this:\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/images\/steve\/2015\/srgb_gamut_surface_08.png>>\r\n%\r\n% Looking at this diagram, you might be able to guess the answer. But,\r\n% let's work through it. The first thing I want to do is to sample the sRGB\r\n% space.\r\n[r,g,b] = ndgrid(linspace(0,1,100));\r\nrgb = [r(:), g(:), b(:)];\r\n\r\n%%\r\n% Next, convert the sRGB colors to Lab. (I'm going to stop typing the \"*\"\r\n% characters.)\r\n\r\nlab = rgb2lab(rgb);\r\n\r\n%%\r\n% Now let's find the colors on the convex hull of the Lab values. The pair\r\n% of colors most distant from each other must both be on the convex hull.\r\n\r\nk = convhull(lab);\r\nhull_colors = lab(unique(k(:,1)),:);\r\n\r\n%%\r\n% If this were a two-dimensional problem, then I know of an algorithm that\r\n% can find the most distant pair of points in $O(N)$, where $N$ is the\r\n% number of points on the convex hull. I don't know of a similar algorithm\r\n% in three dimensions, so I'm just going to brute force it by computing the\r\n% distance between every color pair.\r\n%\r\n% So, how many colors are we talking about?\r\n\r\nsize(hull_colors)\r\n\r\n%%\r\n% There are 3908 colors, each of which is represented by a 3-element\r\n% vector in Lab space. The color difference $\\Delta_E$ is the Euclidean\r\n% distance between two colors in Lab space. I want to compute the\r\n% distance between every color in that set and every other color. Here\r\n% is a nifty and compact way to do that using the implicit expansion\r\n% behavior of MATLAB arithmetic operators.\r\n\r\nC1 = reshape(hull_colors,[],1,3);\r\nC2 = reshape(hull_colors,1,[],3);\r\nCdiff = C1 - C2;\r\nDE = sqrt(sum(Cdiff.^2,3));\r\nsize(DE)\r\n\r\n%%\r\n% You can see that |DE| is an 3980x3980 matrix. It contains the\r\n% pair-wise distances between all the convex hull colors. (See also\r\n% |pdist2|, a function in the Statistics and Machine Learning Toolbox\r\n% that can compute pair-wise vector distances with much more\r\n% flexibility, as well as |hypot|, a MATLAB function that computes the\r\n% magnitude of a two-element vector in a way that avoids numerical\r\n% overflow and underflow problems.)\r\n% \r\n% Let's find where the maximum distances are in this matrix.\r\n\r\n[m,n] = find(DE == max(DE(:)))\r\n\r\n%%\r\n% What are those colors (in Lab space)?\r\n\r\nmax_colors_lab = hull_colors(m,:)\r\n\r\n%%\r\n% And those two colors in sRGB space:\r\n\r\nmax_colors_rgb = lab2rgb(max_colors_lab)\r\n\r\n%%\r\n% They are just the sRGB green and blue primaries.\r\n\r\ncolorSwatches(max_colors_rgb)\r\naxis equal\r\naxis off\r\n\r\n%%\r\n% (|colorSwatches| is a <http:\/\/www.imageprocessingplace.com\/DIPUM-3E\/dipum3e_main_page.htm\r\n% *DIPUM3E*> function that is available to you in _MATLAB Color Tools_ on the\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/64161-matlab-color-tools\r\n% File Exchange> and on <https:\/\/github.com\/mathworks\/matlab-color-tools\r\n% GitHub>.)\r\n%\r\n% These two colors have a $\\Delta_E$ of:\r\n\r\nDE(m(1),n(1))\r\n\r\n%%\r\n% The deltaE function offers a more convenient way to compute\r\n% $\\Delta_E$:\r\n\r\ndeltaE([0 1 0],[0 0 1])\r\n\r\n%%\r\n% Some other time, I will write about the other color difference\r\n% calculations provided by |imcolordiff|.\r\n##### SOURCE END ##### 636a8c37421348b1a0e15a865739af61\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/largest_color_difference_01.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>I wrote previously about the new colorChecker, which can detect X-Rite test charts in the R2020b release. Another area of new color-related functionality is computing perceptual color differences.... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2020\/10\/30\/how-to-compute-perceptual-color-difference\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":4223,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4217"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=4217"}],"version-history":[{"count":4,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4217\/revisions"}],"predecessor-version":[{"id":4233,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4217\/revisions\/4233"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/4223"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=4217"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=4217"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=4217"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}