Steve on Image Processing

Concepts, algorithms & MATLAB

This is machine translation

Translated by Microsoft
Mouseover text to see original. Click the button below to return to the English version of the page.

Pixel grid 16

Posted by Steve Eddins,

I started writing a post today about the ConvexImage property returned by regionprops. But I got sidetracked along the way, and this post turned into a description of how to overlay a pixel grid on an image. (I'll come back to the original ConvexImage idea in a future post.)

Here's what got me sidetracked. I made a tiny little binary image, and then I displayed it with high magnification because I wanted to show the individual pixels clearly as square regions.

bw = [...
    0 0 0 1 0 0 0
    0 0 1 0 1 0 0
    0 1 0 0 0 1 0
    1 1 1 1 1 1 1 ];

imshow(bw, 'InitialMagnification', 'fit')

Well, that doesn't look good! It's hard to see the individual pixels, and there's a whole row of white pixels at the bottom of the image that have disappeared into the background of the page. I decided that it would be nice to see a subtle but visible outline around each pixel. Here's what I have in mind:

Let me show you how to do this in a fairly general fashion. When we're done, maybe I'll have something that would be worth posting to the MATLAB Central File Exchange.

First, I need to get a handle to the image object because I need some information about the x- and y-coordinates of the pixels. I'll use findobj to find the handle for the image object in the current figure.

h = findobj(gcf,'type','image');

The XData and YData properties of the image object determine where the image pixels lie in the axes data space.

xdata = get(h, 'XData')
xdata =

     1     7

ydata = get(h, 'YData')
ydata =

     1     4

This means:

  • The x-coordinate of the center of the first image column is 1.0.
  • The x-coordinate of the center of the last image column is 7.0.
  • The y-coordinate of the center of the first image row is 1.0.
  • The y-coordinate of the center of the last image row is 4.0.

Let's also grab the number of rows and columns of the image.

M = size(get(h,'CData'), 1);
N = size(get(h,'CData'), 2);

Reader challenge: Explain why I didn't use this code:

   [M,N] = size(get(h,'CData'));

Next I want to compute vectors containing the x- and y-locations of the pixel edges.

if M > 1
    pixel_height = diff(ydata) / (M-1);
else
    % Special case. Assume unit height.
    pixel_height = 1;
end

if N > 1
    pixel_width = diff(xdata) / (N-1);
else
    % Special case. Assume unit width.
    pixel_width = 1;
end

y_top = ydata(1) - (pixel_height/2);
y_bottom = ydata(2) + (pixel_height/2);
y = linspace(y_top, y_bottom, M+1)
y =

    0.5000    1.5000    2.5000    3.5000    4.5000

x_left = xdata(1) - (pixel_width/2);
x_right = xdata(2) + (pixel_width/2);
x = linspace(x_left, x_right, N+1)
x =

    0.5000    1.5000    2.5000    3.5000    4.5000    5.5000    6.5000    7.5000

Let me pause here and illustrate how we're going to use these numbers. Here's how to plot the vertical edge separating the 2nd and 3rd columns of pixels.

imshow(bw, 'InitialMagnification', 'fit')
hold on
plot([x(3) x(3)], [y(1) y(end)], 'r', 'LineWidth', 2)
hold off

We could plot the entire pixel grid by using N+1 vertical lines and M+1 horizontal lines. However, MATLAB graphics performance is usually better if you create fewer graphics objects. So I'm going to go to a little more effort in order to create only two line objects. One will snake up and down vertically(xv and yv); the other will snake left and right horizontally (xh and yh).

xv = zeros(1, 2*numel(x));
xv(1:2:end) = x;
xv(2:2:end) = x;

yv = repmat([y(1) ; y(end)], 1, numel(x));
yv(:,2:2:end) = flipud(yv(:,2:2:end));

xv = xv(:);
yv = yv(:);

imshow(bw, 'InitialMagnification', 'fit')
hold on
plot(xv, yv, 'r', 'LineWidth', 2, 'Clipping', 'off')
hold off
title('Vertical pixel edges')
yh = zeros(1, 2*numel(y));
yh(1:2:end) = y;
yh(2:2:end) = y;

xh = repmat([x(1) ; x(end)], 1, numel(y));
xh(:,2:2:end) = flipud(xh(:,2:2:end));

xh = xh(:);
yh = yh(:);

imshow(bw, 'InitialMagnification', 'fit')
hold on
plot(xh, yh, 'r', 'LineWidth', 2, 'Clipping', 'off')
hold off
title('Horizontal pixel edges')

To get all edges, superimpose the vertical and horizontal edges. Also, at this point I'm going to start using the low-level function line instead of plot. This is more robust and cuts out the need to call hold on and hold off.

h = imshow(bw, 'InitialMagnification', 'fit');
ax = ancestor(h, 'axes');
line('Parent', ax, 'XData', xh, 'YData', yh, ...
    'Color', 'r', 'LineWidth', 2, 'Clipping', 'off');
line('Parent', ax, 'XData', xv, 'YData', yv, ...
    'Color', 'r', 'LineWidth', 2, 'Clipping', 'off');
title('All pixel edges')

But what if some of the pixels were red? We wouldn't be able to see the grid. I'm going to use the two-line superposition technique that I described way back on New Year's Day, 2007, and I'm going to switch to thinner lines using shades of gray.

The trick for drawing a line that's guaranteed to be visible against any background is to draw the line twice, using contrasting colors and a solid and a dashed line style.

dark = [.3 .3 .3];
light = [.8 .8 .8];
h = imshow(bw, 'InitialMagnification', 'fit');
ax = ancestor(h, 'axes');
line('Parent', ax, 'XData', xh, 'YData', yh, ...
    'Color', dark, 'LineStyle', '-', 'Clipping', 'off');
line('Parent', ax, 'XData', xh, 'YData', yh, ...
    'Color', light, 'LineStyle', '--', 'Clipping', 'off');
line('Parent', ax, 'XData', xv, 'YData', yv, ...
    'Color', dark, 'LineStyle', '-', 'Clipping', 'off');
line('Parent', ax, 'XData', xv, 'YData', yv, ...
    'Color', light, 'LineStyle', '--', 'Clipping', 'off');

OK, readers, it's function design time. I think this technique could be encapsulated into a nice little utility function on the File Exchange. But before I go ahead and write it and submit, I'd like to open it up for discussion with you.

How about the implementation? The implementation above is basically the first thing I thought of; are there improvements?

How about the function name? I have one in mind, but what would you pick? How about the syntaxes? Input and output arguments? Options?

Share your thoughts.


Get the MATLAB code

Published with MATLAB® 7.11

Note

Comments are closed.

16 CommentsOldest to Newest

Jonas replied on : 1 of 16
I think this can be quite a useful function. I have two suggestions, though: 1. Instead of using snake-like lines, it might be better to write
xv = NaN(1, 3*numel(x));
xv(1:3:end) = x;
xv(2:3:end) = x;

yv = repmat([y(1) , y(end), NaN], 1, numel(x));
Thus, there aren't overlapping lines at the edges of the grid, which might become annoying if you work with the image in e.g. Illustrator. 2. It would be useful, at least for me, to get x and y from the XData and YData properties of the image, because they may not be spaced 1 and may not start at 1. As for the function, a good name might be "pixelGrid", with a syntax like "grid", except that an additional, last input could specify the grid color.
Steve replied on : 2 of 16
Jonas—Thanks for being the first to jump in on this! I thought about using NaN separators but shied away from it because I've always thought that the NaN separator behavior was a bit of a hack. However, my "snake-like" lines could also be regarded as a hack, and I take your point about making the graphics output easier to work with in other applications. Regarding your point #2, the code in this post is getting x and y from the image's XData and YData properties.
João Henriques replied on : 3 of 16
This sounds like a very useful utility. And thanks for showing some great examples of vectorized code and how to play nicely with non-standard X/YData (if I understood correctly, that is ;). I have to admit that I don't usually think much about minimizing the number of patch objects, this sort of technique (snaking, using NaNs if needed) is something I'l have to keep in mind! Also, I agree with the pixelgrid name, it's simple and to the point.
Bjorn replied on : 4 of 16
I don't understand why this should be needed. Isn't exactly the same thing achieved with: (hope I get the formating right...)
qwe = rand(4);
subplot(2,1,1)
imagesc(qwe)
subplot(2,1,2)
pcolor(qwe([1:end,end],[1:end,end]))
axis ij
shading faceted
end
Jos replied on : 5 of 16
Hi Steve, You might want to take a look at my GRIDXY function that works almost the same https://www.mathworks.com/matlabcentral/fileexchange/9973-gridxy
bw = [...
    0 0 0 1 0 0 0
    0 0 1 0 1 0 0
    0 1 0 0 0 1 0
    1 1 1 1 1 1 1 ];

imshow(bw, 'InitialMagnification', 'fit')
lim = get(gca,{'xlim','ylim'})
h = gridxy(lim{1}(1):lim{1}(2),lim{2}(1):lim{2}(2), ...
  'color','r','linestyle',':') ; % a single object
 uistack(h,'top') ; 
Steve replied on : 6 of 16
Bjorn and Jos—Thanks for suggesting alternative methods. Bjorn, your method, although certainly useful, isn't "exactly the same thing." With your method, the grid lines aren't guaranteed to be visible against any pixel color, truecolor image display doesn't work, and the coordinate system is different.
Brett Shoelson replied on : 7 of 16
Hi Steve, Nice, useful function. I'm looking forward to seeing it on the File Exchange. For the name, I'd vote for either pixelGrid or showPixelGrid. And I'm withholding judgement about NaN separators, though Jonas makes a good point. But if you DON'T go that route, I would prefer to see a single line object that snakes both horizontally and vertically. It wouldn't be difficult to ensure that the contour is closed, and I don't think you need to be overly concerned about overlap. (There is already overlap--on the horizontal connectors of the vertical snake, and the vertical connectors of the horizontal snake, right?) That would facilitate your returning a single handle to the "gridLines object," with which we could easily modify its properties.
Loren replied on : 8 of 16
I have my hand raised to answer your challenge (having to do with number of planes in the image perhaps?). But you could avoid two calls to get and size robustly as well, with this code, I think.
[M,N,~] = size(get(h,'CData'));
--Loren
Steve replied on : 9 of 16
Brett—I take your point about the desirability of plotting the grid as a single line object rather than two line objects. I would probably implement that using the NaN separator approach instead of fancier snake shape, simply because I'd be more confident of getting it right. :-) Still, I wouldn't be able to return the result as a single line object handle because of my trick of drawing two lines with contrasting colors and linestyles.
Matt K. replied on : 11 of 16
Great stuff! I made my own "imgrid" subroutine several years ago, although it is far less elegant than what is described here since I use a looped 'line' command to get through the rows and cols. I use my program primarily to check binary masks that cover a small region of a 512x512 image. To help with the slower graphics caused by the numerous lines, I limit my axis display to:
axis([min(cc(:))-1.5 max(cc(:))+1.5 min(rr(:))-1.5 max(rr(:))+1.5]); 
(where 'rr' and 'cc' are the corresponding row and column numbers of valid mask pixels), so I never have to pan and slowdown the display Also, I use
 
im=getimage(gcf);
[nr nc]=size(im); 
since that's what I knew at the time about getting the size of any image in the current figure I vote for the name of the subroutine in the next version of the imaging toolbox to be "imgrid"! -Matt K.
Mark Hayworth replied on : 12 of 16
Steve: I think it could be useful. We could recommend it when people (in the newsgroup) try to use the awful pcolor() for the same thing and get unexpected results because pcolor is so bad and confusing, what with having one less tile than the number of pixels, weird colormap, etc.
Raja replied on : 13 of 16
Hi Steve, If i understood your problem correctly i think there is function 'impixelregion' in image processing toolbox that does the same thing. The only problem with this function is that the grid has to be set interactively by the user. Finally, also for the function you are developing it will be useful if you can display the pixel values.
Steve replied on : 14 of 16
Raja—Yes, I am familiar with impixelregion. In fact, I wrote it. ;-) The functions have similar motivations but do not do "the same thing." The capability shown here is more suitable for noninteractive use, especially for published or exported graphics, or for use as a low-level graphics tool in combination with other kinds of graphics objects an visualization.
Shima replied on : 15 of 16
hi, i read this post and it was useful for my project, but i have 2 questions, 1, how to draw 2 pixel grid in one figure? 2, how to change the colors of black and white? i want to implement ant colony algorithm with obstacles and path for ants. would you help me please? thanks in advance.
Steve Eddins replied on : 16 of 16
Shima—1. Follow the steps in this post twice. 2. See the line properties documentation for how to set the color of a line.