Help explain interpolated surface coloring

2 vues (au cours des 30 derniers jours)
Tim
Tim le 30 Août 2024
Commenté : Eric Ludlam le 23 Sep 2024
I have the follwoing 11x11 data grid:
x = 0:10
x = 1x11
0 1 2 3 4 5 6 7 8 9 10
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
y = 0:10
y = 1x11
0 1 2 3 4 5 6 7 8 9 10
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
data = [0.489352000000000 0.484068000000000 0.511895000000000 0.529349000000000 0.550050000000000 0.589397000000000 0.607263000000000 0.641485000000000 0.662575000000000 0.687064000000000 0.713925000000000
0.507529000000000 0.527680000000000 0.538550000000000 0.557169000000000 0.577756000000000 0.614422000000000 0.657682000000000 0.676427000000000 0.684739000000000 0.714883000000000 0.738783000000000
0.538647000000000 0.548932000000000 0.566195000000000 0.580673000000000 0.618064000000000 0.654818000000000 0.689324000000000 0.702041000000000 0.719743000000000 0.733533000000000 0.764315000000000
0.556159000000000 0.573411000000000 0.596687000000000 0.623561000000000 0.671057000000000 0.689452000000000 0.714168000000000 0.730792000000000 0.748139000000000 0.774988000000000 0.788946000000000
0.590044000000000 0.600503000000000 0.634420000000000 0.648230000000000 0.685438000000000 0.721476000000000 0.746399000000000 0.759050000000000 0.782877000000000 0.790311000000000 0.816816000000000
0.620506000000000 0.638116000000000 0.665363000000000 0.676466000000000 0.716868000000000 0.749803000000000 0.754542000000000 0.779754000000000 0.801076000000000 0.827632000000000 0.854378000000000
0.663773000000000 0.672726000000000 0.696712000000000 0.710226000000000 0.735066000000000 0.747308000000000 0.775699000000000 0.807429000000000 0.828759000000000 0.855226000000000 0.875827000000000
0.695315000000000 0.709644000000000 0.726708000000000 0.740776000000000 0.768420000000000 0.799622000000000 0.796054000000000 0.825712000000000 0.846637000000000 0.876592000000000 0.897063000000000
0.719426000000000 0.727156000000000 0.748276000000000 0.762108000000000 0.796051000000000 0.804258000000000 0.813496000000000 0.820555000000000 0.868153000000000 0.888011000000000 0.908845900000000
0.743718000000000 0.754901000000000 0.765970000000000 0.776975000000000 0.811003000000000 0.825777000000000 0.828351000000000 0.841359000000000 0.869123000000000 0.903499400000000 0.914743600000000
0.765627000000000 0.770041000000000 0.787120000000000 0.801125000000000 0.823051000000000 0.837764000000000 0.845964000000000 0.862046000000000 0.883647000000000 0.895037000000000 0.919736800000000]
data = 11x11
0.4894 0.4841 0.5119 0.5293 0.5501 0.5894 0.6073 0.6415 0.6626 0.6871 0.7139 0.5075 0.5277 0.5385 0.5572 0.5778 0.6144 0.6577 0.6764 0.6847 0.7149 0.7388 0.5386 0.5489 0.5662 0.5807 0.6181 0.6548 0.6893 0.7020 0.7197 0.7335 0.7643 0.5562 0.5734 0.5967 0.6236 0.6711 0.6895 0.7142 0.7308 0.7481 0.7750 0.7889 0.5900 0.6005 0.6344 0.6482 0.6854 0.7215 0.7464 0.7591 0.7829 0.7903 0.8168 0.6205 0.6381 0.6654 0.6765 0.7169 0.7498 0.7545 0.7798 0.8011 0.8276 0.8544 0.6638 0.6727 0.6967 0.7102 0.7351 0.7473 0.7757 0.8074 0.8288 0.8552 0.8758 0.6953 0.7096 0.7267 0.7408 0.7684 0.7996 0.7961 0.8257 0.8466 0.8766 0.8971 0.7194 0.7272 0.7483 0.7621 0.7961 0.8043 0.8135 0.8206 0.8682 0.8880 0.9088 0.7437 0.7549 0.7660 0.7770 0.8110 0.8258 0.8284 0.8414 0.8691 0.9035 0.9147
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
[xgrid, ygrid] = meshgrid(x,y)
xgrid = 11x11
0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
ygrid = 11x11
0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
When dsplayed as a surface with facecolor = 'interp', I get slightly differenent results depending on if my data is organized "top to bottom" or "bottom to top." Why? The values at each x-y vertex of a cell are identical. I assumed MATLAB was perfoming a linear interpolation for the coloring within each cell. But these results indicate something unknown...
figure;
colormap(jet(32))
surface(xgrid, ygrid, data, EdgeAlpha=0.2, FaceColor='interp');
colorbar
figure;
colormap(jet(32))
surface(flipud(xgrid), flipud(ygrid), flipud(data), EdgeAlpha=0.2, FaceColor='interp');
colorbar

Réponse acceptée

John D'Errico
John D'Errico le 30 Août 2024
Modifié(e) : John D'Errico le 31 Août 2024
Without looking deeply into the method used to interpolate, I can't absolutely know what their code does. However, I do deeply understand interpolation in 1 and more dimensions, and what is commonly done. (In a previous incarnation, in a galaxy far, far away, I spent a fair amount of time learning about interpolation artifacts in multiple dimensions, why they arise, etc.)
First, let me point out that linear interpolation on a problem with two independent variables (so z(x,y)) requires three points. That is, THREE points determine a plane. And a linear interpolation implicitly assumes a plane. So what happens? What is done when you have more than 3 points in a rectangular lattice? After all, a rectangle has FOUR points, not three. The difference is huge. Well, it can be significant.
Consider a simple example in two dimensions, on the unit square [0,1]X[0,1]. I'll pick some values for the 4 corners of that square, where there is an intentional nonlinearity. No single plane will pass through all 4 points.
Fij = [0 3;1 1.5]
Fij = 2x2
0 3.0000 1.0000 1.5000
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
First, I'll plot that array using what people often call a bilinear interpolant.
F = @(x,y) Fij(1,1)*(1-x).*(1-y) + Fij(1,2)*(1-x).*y + Fij(2,1)*x.*(1-y) + Fij(2,2)*x.*y;
fsurf(F,[0 1 0 1])
And what you see is a curved surface. Again, nothing you can do will make that curved thing into a plane. And while you talked about doing "linear" interpolation, I think tyou do not really understand linear interpolation, and what happens in more than one dimension. What the function F implements is what is sometimes called bilinear interpolation. It truly is linear if you look along any line parallel to either axis, but that is as much as you can do.
Instead, the common approach is to dissect each square in a rectangular grid into a pair of triangles. Perhaps something like this:
trimesh([1 2 4;1 3 4],[0;0;1;1],[0;1;0;1],[0;3;1;1.5])
The result is two planes.
trimesh([1 2 3;2 3 4],[0;0;1;1],[0;1;0;1],[0;3;1;1.5])
In all cases, the 4 corners of that square have identical function values. But we get three different surfaces. In cases 2 and 3, the only difference is how I oriented the triangular dissection of the unit square.
Now, what happens when we plot a surface on a grid? MATLAB will choose a specific dissection, breaking EACH rect in that grid into two triangles. I don't know which way the dissection is done internally of course. It might be this one:
Or it might be one where the triangles are oriented 90 degrees to that, thus...
What matters is the choice made, and I cannot see into their internal code. But the choice made will be consistent internally. It will be one of those two alternatives.
But what happens when you flip the data along one axis? Implicitly the triangulation is flipped compared to your data. They are still using the same internal dissection. But your data is flipped.
And that means the interpolation used to interpolate the color shading changes, in a subtle way. As you can see, those 2nd and 3rd surfaces are actually pretty significantly different. Enough so that when you look at the resulting coloration as generated, we will see the differences, because those differences, subtle as they may seem, will impact where the color quantization changes.
Now, let me look at your specific data.
data = [0.489352000000000 0.484068000000000 0.511895000000000 0.529349000000000 0.550050000000000 0.589397000000000 0.607263000000000 0.641485000000000 0.662575000000000 0.687064000000000 0.713925000000000
0.507529000000000 0.527680000000000 0.538550000000000 0.557169000000000 0.577756000000000 0.614422000000000 0.657682000000000 0.676427000000000 0.684739000000000 0.714883000000000 0.738783000000000
0.538647000000000 0.548932000000000 0.566195000000000 0.580673000000000 0.618064000000000 0.654818000000000 0.689324000000000 0.702041000000000 0.719743000000000 0.733533000000000 0.764315000000000
0.556159000000000 0.573411000000000 0.596687000000000 0.623561000000000 0.671057000000000 0.689452000000000 0.714168000000000 0.730792000000000 0.748139000000000 0.774988000000000 0.788946000000000
0.590044000000000 0.600503000000000 0.634420000000000 0.648230000000000 0.685438000000000 0.721476000000000 0.746399000000000 0.759050000000000 0.782877000000000 0.790311000000000 0.816816000000000
0.620506000000000 0.638116000000000 0.665363000000000 0.676466000000000 0.716868000000000 0.749803000000000 0.754542000000000 0.779754000000000 0.801076000000000 0.827632000000000 0.854378000000000
0.663773000000000 0.672726000000000 0.696712000000000 0.710226000000000 0.735066000000000 0.747308000000000 0.775699000000000 0.807429000000000 0.828759000000000 0.855226000000000 0.875827000000000
0.695315000000000 0.709644000000000 0.726708000000000 0.740776000000000 0.768420000000000 0.799622000000000 0.796054000000000 0.825712000000000 0.846637000000000 0.876592000000000 0.897063000000000
0.719426000000000 0.727156000000000 0.748276000000000 0.762108000000000 0.796051000000000 0.804258000000000 0.813496000000000 0.820555000000000 0.868153000000000 0.888011000000000 0.908845900000000
0.743718000000000 0.754901000000000 0.765970000000000 0.776975000000000 0.811003000000000 0.825777000000000 0.828351000000000 0.841359000000000 0.869123000000000 0.903499400000000 0.914743600000000
0.765627000000000 0.770041000000000 0.787120000000000 0.801125000000000 0.823051000000000 0.837764000000000 0.845964000000000 0.862046000000000 0.883647000000000 0.895037000000000 0.919736800000000];
x = 0:10;
y = 0:10;
[xgrid, ygrid] = meshgrid(x,y);
A big part of the problem, what probably exacerbates the issure, is you are trying to use a highly quantized colormap, thus jet(32). If you had used a finer colormap, perhaps jet(256), the differences would be far less obvious.
figure
colormap(jet(256))
surface(xgrid,ygrid, data, EdgeAlpha=0.2, FaceColor='interp');
  3 commentaires
John D'Errico
John D'Errico le 3 Sep 2024
There are always many factors, many tradeoffs. Bilinear will surely be slower. And if you need speed ... when doing graphics, speed is arguably pretty important. And bilinear interpolants have their own interesting set of interpolation artifacts, that can look pretty wacky under the right circumstances.
I'd say the triangulated dissection is a common one. For example, if you ever do a contour plot, and look VERY carefully at what it produces, you will realize there was a triangulation employed under the hood. (Far easier to generate contours from a triangulated dissection then for a bilinearly interpolated surface. That gets a bit more nasty.) And this way, the result of a color shading will always be consistent with a contour plot. In my eyes, that would seem a very good thing, since it is common for people to produce shaded contour plots.
Finally, I'd point out that color management modules (the things that work under the hood of your computer/color printer when printing color graphics will generally use a similar triangulated dissection, but in 3-d, because they need to do interpolation too. And there they want pure, flat out speed. But the artifacts generated from a trilinear interpolant were significant in terms of color mappings.
Its been a long long time since I cared, but I think I recall that Adobe Photoshop uses bilinear when resizing images and a linear interpolant is requested. (30 years ago, so I may well be wrong by now.)
The point of all this is, these choices are often dictated by many factors.
Eric Ludlam
Eric Ludlam le 23 Sep 2024
John provided a really nice answer here, and is correct in the assumption that surface needs to convert the quadrilaterals within it's mesh into triangles. The process is relatively simple, and is done as the data is converted into something the graphics card can consume and draw quickly.
@Tim, you mentioned "the default 64 color parula colormap". The default should be 256 colors unless you are using a release prior to R2019b.
Here are two simple surfaces that show more directly how the quadrilateral is converted into triangles, and how the triangulation choice changes how interpolated colors are applied.
colormap([1 0 0; 0 0 1]);
tl=tiledlayout(1,2);
nexttile
surf([0 1],[0 1],[1 0; 0 1],[0 0; 1 0],'facec','i');
daspect([1 1 1])
nexttile
surf([0 1],[0 1],[1 1; 0 1],[0 0; 0 1],'facec','i');
daspect([1 1 1])
The above is colored with a colormap which provides hard edges between the colors in the map. This is done by creating a texturemap (a 1 dimensional image) and we ask the graphics card to texture that onto the triangles. Most graphics cards are designed for video games, so using textures is a very fast operation.
When you provide RGB values for the colors instead, the interpolation happens in RGB space which we also delegate to the graphics card, and you get something that looks like this instead:
tl=tiledlayout(1,2);
nexttile
C = zeros(2,2,3);
C(:,:,1) = 1; % red
C(2,1,:) = [0 0 1]; % blue
surf([0 1],[0 1],[1 0; 0 1],C,'facec','i');
daspect([1 1 1])
nexttile
C = zeros(2,2,3);
C(:,:,1) = 1; % red
C(2,2,:) = [0 0 1]; % blue
surf([0 1],[0 1],[1 1; 0 1],C,'facec','i');
daspect([1 1 1])
Which is nice if you don't mind the artifacts that come with using RGB interpolation.

Connectez-vous pour commenter.

Plus de réponses (0)

Produits


Version

R2022b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by