Skip to Main Content Skip to Search
File Exchange
MATLAB Newsgroup
Link Exchange
  Blogs  
 Contest 
MathWorks.com

Steve on Image Processing

October 8th, 2008

Getting updates on reported bugs

Did you know that MathWorks publishes information about product bugs on its web site? And that you can be automatically notified with updates regarding specific bugs, or regarding specific products?

A few weeks ago, Joan posted a blog comment about strange behavior in the deconvwnr function. I investigated and determined that there was a bug in some code that was intended to avoid divide-by-zero warnings. I fixed the problem and then published a bug report, including a work-around, to our support site. (The fix was too late to be included in the upcoming R2008b release, so it will be in R2009a.)

The bug report includes a summary of the problem, which product releases have the problem, whether the problem has been fixed in a shipped release, and whether there is a work-around. In the case of the deconvwnr problem, there is a work-around, and the report includes instructions for downloading new versions of two M-files.

To search the online bug reports, start at the MathWorks home page, then click on Support, and then click on View and Report Bugs. From this page you can search the bug reports, look at bug reports for specific products, set up a watch list, and more. You can sign up to receive e-mail whenever a specific bug report is updated, or you can subscribe to RSS feed, customized to the list of products you are interested in.

There is also a "Frequently Asked Questions" page about bug reports that explains, for example, what an "OPEN" status means, or why some bugs aren't shown in the bug reports.

We are continually trying to improve the quality of our products and therefore reduce the need for published bug reports. In my area, there are fewer unfixed bugs in the development builds of the Image Processing Toolbox and the Mapping Toolbox than at any previous time in the history of these products. But any number of unfixed bugs is too many, and we're trying to do better for you.

Note: Most of the time, you should report product bugs directly to MathWorks technical support rather trying to post them as comments on this or other MATLAB Central blogs. My usual practice is to delete any comments that aren't directly relevant to the topic of the blog post. Joan's comment was relevant to the topic, so I let it through.

September 29th, 2008

Dilation algorithms—decomposing more shapes

In my previous post on dilation algorithms I discussed structuring element decomposition. We looked at the decomposition of structuring elements with rectangular domains. Today I want to follow up on the idea by looking at decompositions of other structuring element shapes.

The strel function offers a variety of flat structuring element shapes:

  • diamond
  • disk
  • line
  • octagon
  • pair
  • periodicline
  • rectangle
  • square

(The strel function also offers nonflat shapes, and shapes with arbitrary domains.)

Let's look at the 'diamond' shape. The doc says SE = strel('diamond', R) creates a flat, diamond-shaped structuring element, where R specifies the distance from the structuring element origin to the points of the diamond." For example:

se1 = strel('diamond', 5)
 
se1 =
 
Flat STREL object containing 61 neighbors.
Decomposition: 4 STREL objects containing a total of 17 neighbors

Neighborhood:
     0     0     0     0     0     1     0     0     0     0     0
     0     0     0     0     1     1     1     0     0     0     0
     0     0     0     1     1     1     1     1     0     0     0
     0     0     1     1     1     1     1     1     1     0     0
     0     1     1     1     1     1     1     1     1     1     0
     1     1     1     1     1     1     1     1     1     1     1
     0     1     1     1     1     1     1     1     1     1     0
     0     0     1     1     1     1     1     1     1     0     0
     0     0     0     1     1     1     1     1     0     0     0
     0     0     0     0     1     1     1     0     0     0     0
     0     0     0     0     0     1     0     0     0     0     0

 

The display says the structuring element has 61 neighbors in its domain, but the four decomposed structuring elements have a total of only 17 neighbors.

What shapes makes up the decomposition? We can use getsequence and getnhood to find out.

se1_seq = getsequence(se1);
for k = 1:numel(se1_seq)
    getnhood(se1_seq(k))
ans =

     0     1     0
     1     1     1
     0     1     0

ans =

     0     1     0
     1     0     1
     0     1     0

ans =

     0     0     1     0     0
     0     0     0     0     0
     1     0     0     0     1
     0     0     0     0     0
     0     0     1     0     0

ans =

     0     1     0
     1     0     1
     0     1     0

end

This decomposition comes from Rein van den Boomgard and Richard van Balen, "Methods for Fast Morphological Image Transforms Using Bitmapped Binary Images," Computer Vision, Graphics, and Image Processing: Models and Image Processing, vol. 54, no. 3, May 1992, pp. 252-254. (That journal name sure is a mouthful!)

Now let's look at the 'disk' structuring element. By default, strel('disk',R) actually gives you an approximation to a disk shape, where the approximation is based on a technique called radial decomposition using periodic lines. The reference sources are:

  • Rolf Adams, "Radial Decomposition of Discs and Spheres," Computer Vision, Graphics, and Image Processing: Graphical Models and Image Processing, vol. 55, no. 5, September 1993, pp. 325-332.
  • Ronald Jones and Pierre Soille, "Periodic lines: Definition, cascades, and application to granulometries," Pattern Recognition Letters, vol. 17, 1996, 1057-1063.
se2 = strel('disk', 5)
 
se2 =
 
Flat STREL object containing 69 neighbors.
Decomposition: 6 STREL objects containing a total of 18 neighbors

Neighborhood:
     0     0     1     1     1     1     1     0     0
     0     1     1     1     1     1     1     1     0
     1     1     1     1     1     1     1     1     1
     1     1     1     1     1     1     1     1     1
     1     1     1     1     1     1     1     1     1
     1     1     1     1     1     1     1     1     1
     1     1     1     1     1     1     1     1     1
     0     1     1     1     1     1     1     1     0
     0     0     1     1     1     1     1     0     0

 

Similar to the 'diamond' shape above, the 'disk' shape is decomposed into a sequence of smaller structuring elements:

se2_seq = getsequence(se2);
for k = 1:numel(se2_seq)
    getnhood(se2_seq(k))
ans =

     1
     1
     1

ans =

     1     0     0
     0     1     0
     0     0     1

ans =

     1     1     1

ans =

     0     0     1
     0     1     0
     1     0     0

ans =

     1     1     1

ans =

     1
     1
     1

end

I mentioned above that strel('disk',R) gives you an approximation to a disk. Let's see what that approximation looks like with a larger radius:

se3 = strel('disk', 21);
imshow(getnhood(se3), 'InitialMagnification', 'fit')

Well, that's definitely an approximation. It looks a lot more like an octagon than a circle. The syntax strel('disk',R,N) gives you more control over the approximation. N can be 0, 4, 6, or 8. The values 4, 6, and 8 indicate different levels of approximation. The default value is 4. With N equal to 8, the result looks closer to a circle, but dilation is a little slower because the decomposition isn't as efficient.

se4 = strel('disk', 21, 8);
imshow(getnhood(se4), 'InitialMagnification', 'fit')

Setting N = 0 asks strel to construct a disk shape with no decomposition approximation.

se5 = strel('disk', 21, 0);
imshow(getnhood(se5), 'InitialMagnification', 'fit')

Using getsequence we can see that there is no decomposition for se5:

numel(getsequence(se5))
ans =

     1

Next time we'll do some timing experiments with the decomposed structuring elements.


Get the MATLAB code

Published with MATLAB® 7.6

September 19th, 2008

Recent changes in MATLAB Central blogs

Do you read any of the other MATLAB Central blogs? There's a lot of good information on them.

For several years, the "Pick of the Week" blog has regularly highlighted the best contributions on the MATLAB Central File Exchange. Until recently, it was "Doug's Pick of the Week." As regular "pick" readers know, Doug shifted his emphasis some time ago to making tutorial videos about MATLAB, while guest bloggers Bob, Brett, and Jiro continued giving you the best of the File Exchange.

These four MathWorkers have now split their blogging into two blogs. The "File Exchange Pick of the Week" is still at the same location and is jointly run by Bob, Brett, and Jiro. And Doug is now publishing his tutorial videos on the new "Doug's MATLAB Video Tutorials" blog.

When the "Inside the MATLAB Desktop" blog was originally started, we thought it would written by entire Desktop team. That never really happened, though. The blog was always run by one or two people, with occasional guest contributions. Most recently, Ken and Mike have been running things, so the Desktop blog has been retitled "Ken & Mike on the MATLAB Desktop." Other MathWorks developers will still occasionally contribute.

"Loren on the Art of MATLAB" has long been our most popular blog and is chock full of valuable tips on programming in MATLAB. And if you're into the Simulink side of our product family, be sure to check out "Seth on Simulink."

Each of our blogs have links on the sidebar for subscribing, either by e-mail or by RSS feed. You might also want to check out the "Blog Archives" links, since most of the technical topics don't get dated and are just as informative now as when they were first written.

What suggestions do you have for our blogs? Do you have any ideas for what new blogs you'd like to see? Let us know by commenting on this post.

September 17th, 2008

Dilation algorithms—structuring element decomposition

This is the second post in my series on algorithm concepts behind the implementation of dilation and erosion in the Image Processing Toolbox. Today I want to talk about structuring element decomposition.

Some useful structuring elements can be represented as the dilation of two or more smaller structuring elements. Dilation implementations can exploit this decomposition of structuring elements into smaller structuring elements by taking advantage of the associativity property of dilation.

That is, suppose image g is the dilation of image f with structuring element b:

where the star operator means dilation. And further suppose that b is the dilation of two other structuring elements:

Then the original image dilation equation can be rewritten as:

In English, the equation above says we can dilate f with structuring element b by:

1. Dilating f first with structuring element b1.

2. Dilating the result of step 1 with structuring element b2.

Let's see how this might help make dilation faster. One of the most commonly-used structuring element shapes is the square. For example, here's a simple 5-by-5 array of 1s that can define a structuring element domain (or neighborhood):

b = ones(5, 5)
b =

     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1

It turns out that a square (or rectangular) structuring element can be decomposed into two structuring elements: a horizontal line, and a vertical line.

b1 = ones(1, 5)
b1 =

     1     1     1     1     1

b2 = ones(5, 1)
b2 =

     1
     1
     1
     1
     1

You can check that the dilation of b1 and b2 equals b by using the 'full' option of imdilate:

imdilate(b1, b2, 'full')
ans =

     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1

How does this help us? Well, as I discussed last time, computing dilation directly from its defining equation requires Q comparisons per pixel, where Q is the number of elements in the structuring element domain. The 5-by-5 square structuring element has 25 elements in its domain. But structuring elements b1 and b2 have only five elements each. So you could dilate by one and then again by the other and require only 40% of the comparisons.

Let's look at how structuring element decompositions are computed and represented in the Image Processing Toolbox. The function strel makes a structuring element object. You give strel the name of a shape, such as 'rectangle', plus any parameters required for the shape. When you use strel to make a rectangular structuring element, the resulting object displays information about itself, including the decomposition:

se = strel('rectangle', [5 5])
 
se =
 
Flat STREL object containing 25 neighbors.
Decomposition: 2 STREL objects containing a total of 10 neighbors

Neighborhood:
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1
     1     1     1     1     1

 

You can call the function getsequence (actually a method of the strel class) to get the structuring elements in the decomposition:

decomp = getsequence(se)
 
decomp =
 
2x1 array of STREL objects
 

You use ordinary MATLAB array indexing to get each of the structuring elements from the decomposition:

decomp(1)
 
ans =
 
Flat STREL object containing 5 neighbors.

Neighborhood:
     1
     1
     1
     1
     1

 
decomp(2)
 
ans =
 
Flat STREL object containing 5 neighbors.

Neighborhood:
     1     1     1     1     1

 

Ordinarily, of course, strel and imdilate together handle all of these details for you when you perform image dilation.

Next time I plan to look at some of the other shapes offered by strel and see how they get decomposed.


Get the MATLAB code

Published with MATLAB® 7.6

September 11th, 2008

Independent computation in software tests

Today's post is brought to you directly from the software developer half of my brain.

MATLAB 6 was the first version to use the FFTW library for fast Fourier transform computations, and I was one of the developers who worked on that change.

Making changes to fft and related functions made me a bit nervous. What if we made a mistake and broke it somehow? That would be 15 minutes of fame I would definitely not enjoy.

The best way to proceed when changing existing, working code is to make sure first that you have thorough, effective tests. (See, for example, "The First Step in Refactoring" in Martin Fowler's book, Refactoring: Improving the Design of Existing Code.) And for numerical computation, nothing gives you more confidence in your tests than using independent computation methods to validate your code.

Besides using fft, I can think of at least two other reasonably easy ways to compute the discrete Fourier transform in MATLAB. One is to write an M-file that directly implements the discrete Fourier transform equation. Another is to use a matrix-vector multiply with the Fourier matrix (see the doc for the dftmtx function in the Signal Processing Toolbox.) Normally, you would never want to use either of these methods when you can instead use a much faster fast Fourier transform algorithm.

However, pure speed is not so critical for testing purposes. Many of our automated tests for fft and related functions test their output against the result found using the matrix-vector multiplication. That turned out to be very helpful for making sure that our changes to fft did not result in incorrect output. (If you'd like to know more about MATLAB and FFTW, read this Cleve's Corner.)

Here's another testing example illustrating the need for independent computation. The Mapping Toolbox has functions for computing minimum distance curves on an ellipsoid. No closed-form solutions exist, so series or iterative approximations are commonly used. The Mapping Toolbox test suite checks the numerical accuracy of these approximations by comparing them to the results obtained using MATLAB ODE solvers. (This testing technique was published in a paper. See R. Comer and N. Ramarathnam, "Numerical Validation of Curves on the Ellipsoid," Proceedings of the ASPRS 2005 Annual Conference.)

Often you can independently compute small positive test cases by hand. Many image morphology test cases in the Image Processing Toolbox test suite were computed by hand directly from the relevant formulas and definitions.

It's tempting sometimes, especially for operations that are especially tedious to compute by hand or by another method, to construct test case validation data by saving the output data from the code you are trying to test. Such a test is only useful for making the sure that today's output from the code is the same as yesterday's; it doesn't tell you anything about whether the answers are correct. That's better than no test at all, but not nearly as good as independent validation of correctness.

September 5th, 2008

Escher, images, and chess

My blog has a rule, which I just now made up, that my second post during the month of September has to be totally off topic.

So you've been warned!

OK, you're still reading, so I want to ask you: Do you like to play chess?

A few years ago I was sitting in another developer's office here, staring idly at the Escher print on her wall. This print was a long, thin strip that went almost all the way around the top of the office walls. It showed many different geometric motifs merging from one form to another, in typical Escher fashion. It suddenly dawned on me that the chess pieces in one section of the print were arranged in a real chess position.

Here it is:

Escher chess board

So how about it, image processing / chess fans? What's going on in this position? Who is winning, and what are the next moves going to be?

Next time I'll return to discussing image processing, I promise.

September 3rd, 2008

Dilation algorithms—Introduction

Post updated on September 4 based on feedback from Cris Luengo's comment.

Today I'm starting a new series covering some of the concepts underlying algorithms for performing morphological dilation (and erosion, which is very similar). Most of the time, when people talk about image dilation, they mean the form of dilation that is a local maximum operation on the neighbors of each pixel. For example, here's how to compute the local maximum, for each image pixel, with that pixel and its eight neighbors:

A = magic(5)
A =

    17    24     1     8    15
    23     5     7    14    16
     4     6    13    20    22
    10    12    19    21     3
    11    18    25     2     9

se = ones(3,3)
se =

     1     1     1
     1     1     1
     1     1     1

B = imdilate(A, se)
B =

    24    24    24    16    16
    24    24    24    22    22
    23    23    21    22    22
    18    25    25    25    22
    18    25    25    25    21

The set of neighborhood pixels involved in the max operator forms a shape called the structuring element. The structuring element doesn't have to be square, as above. Here is dilation with a short, 3-pixel line:

se = [1 1 1];
B2 = imdilate(A, se)
B2 =

    24    24    24    15    15
    23    23    14    16    16
     6    13    20    22    22
    12    19    21    21    21
    18    25    25    25     9

Let's consider how long it should take to compute dilation. Suppose an image has P pixels, and the structuring element contains Q neighbors. Computing the output for a particular pixel involves both Q memory reads and Q comparisons. Therefore we might expect the computation time to be proportional to Q.

We can try to verify that behavior using square structuring elements of varying sizes. (I'll be using my timeit function, which you can download from the MATLAB Central File Exchange.)

% Read in a small sample image and replicate it to make a 1k-by-1k
% test image.
I = repmat(imread('rice.png'), 4, 4);

% Measure dilation times using a set of square structuring elements
% ranging in size from 5-by-5 to 97-by-97.
w = 5:4:97;

% The number of neighbors, Q, in each structuring element will be
% w^2.  We are guessing that the running time will be proportional
% to Q.
Q = w.^2;

% Initialize vector containing recorded times.
times = [];

for wk = w
    % Not that many MATLAB users are familiar with the for-loop
    % construct above.  I'll send a MATLAB t-shirt to the first
    % person who adds a comment to this post with a brief, clear,
    % and correct explanation.

    se = strel(ones(wk, wk));
    f = @() imdilate(I, se);
    times(end+1) = timeit(f);
end

plot(Q, times)
ylim([0 1.5*max(times)])

That plot looks almost constant, with maybe just a hint of an increase with Q.

So is imdilate somehow computing dilation faster than expected?

That's the subject of this series. We'll start digging in deeper next time.


Get the MATLAB code

Published with MATLAB® 7.6

August 20th, 2008

Image visualization using transparency

Transparent graphics objects can be used effectively to visualize image processing concepts. Two particularly useful techniques are:

  • Highlighting image regions with transparent patches
  • Displaying one image transparently over another

Today I'll show how to highlight image regions with patches. For this example I'll use the 'Extrema' measurement returned by regionprops. The extrema for a given object are eight points: the left-most pixel on the bottom, the right-most pixel on the bottom, the top-most pixel on the right, the bottom-most pixel on the right, and so on.

I'll start with the rice image, segmenting it using techniques I've shown before.

I = imread('rice.png');
imshow(I)

Even out the illumination with the tophat operator, threshold, and then clean up the thresholded image a bit.

I2 = imtophat(I, ones(15, 15));
bw = im2bw(I2, graythresh(I2));
bw2 = bwareaopen(bw, 5);
bw3 = imclearborder(bw2);
imshow(bw3)

Label the binary objects and compute the extrema.

L = bwlabel(bw3);
s = regionprops(L, 'Extrema');

Each object has 8 extrema points associated with it.

s(1).Extrema
ans =

   11.5000   86.5000
   12.5000   86.5000
   34.5000  100.5000
   34.5000  102.5000
   33.5000  103.5000
   27.5000  103.5000
    9.5000   89.5000
    9.5000   87.5000

We can superimpose the extrema-bounded shapes on top of the original rice image by using patch objects.

imshow(I)

hold on
for k = 1:numel(s)
    x = s(k).Extrema(:,1);
    y = s(k).Extrema(:,2);
    patch(x, y, 'g')
end
hold off

The above visualization is pretty clear. If you zoom in on some of the odd, larger shapes, though, you can't really tell what's going on.

axis([120 200 1 75])

We solve that by displaying the patches transparently.

imshow(I)

hold on
for k = 1:numel(s)
    x = s(k).Extrema(:,1);
    y = s(k).Extrema(:,2);
    patch(x, y, 'g', 'FaceAlpha', 0.3)
end
hold off

Now if you zoom in on the same region, we can see exactly what caused the unusual region.

axis([120 200 1 75])

Two of the rice grains were touching.

Next time I'll show a couple of techniques for visualizing one image transparently superimposed on another.


Get the MATLAB code

Published with MATLAB® 7.6

August 5th, 2008

Filling small holes

A MATLAB user recently asked in the MATLAB newsgroup how to fill "small" holes in a binary image. The function imfill can be used to fill all holes, but this user only wanted to fill holes having an area smaller than some threshold.

That's an interesting question. It can be done using a combination of imfill, bwareaopen, and MATLAB logical operators. Here's how.

Step 1: Fill all holes using imfill:

original = imread('circbw.tif');
imshow(original)
filled = imfill(original, 'holes');
imshow(filled)
title('All holes filled')

Step 2: Identify the hole pixels using logical operators:

holes = filled & ~original;
imshow(holes)
title('Hole pixels identified')

Step 3: Use bwareaopen on the holes image to eliminate small holes:

bigholes = bwareaopen(holes, 200);
imshow(bigholes)
title('Only the big holes')

Step 4: Use logical operators to identify small holes:

smallholes = holes & ~bigholes;
imshow(smallholes)
title('Only the small holes')

Step 5: Use a logical operator to fill in the small holes in the original image:

new = original | smallholes;
imshow(new)
title('Small holes filled')

All done!


Get the MATLAB code

Published with MATLAB® 7.6

July 30th, 2008

Reading Massively Multipage TIFFs: An Update

Last fall I wrote about a complaint some MATLAB users had about reading certain kinds of TIFF files. Specifically, some users have TIFF files that each contain tens of thousands of images—or more! One user sent us a single TIFF file containing almost 130,000 individual images. My tongue-in-cheek name for these files is "Massively Multipage TIFF" (MMT).

The problem is that the syntax for reading the k-th image from a TIFF file:

  >> I = imread(filename, k);

takes significantly longer for images at the end of the file. As a result, reading all of the images in such a file takes way too long. I discussed the technical issues in the post "How many images can fit in a TIFF file" last September.

We now have updated files you can use to improve import performance on these TIFF files. The necessary files and instructions can be downloaded here. The patch is only for the most recent MATLAB release, R2008a. Please do not try to apply the patch to earlier releases.

[Update - August 11, 2008 - The download link above now points to an official MathWorks technical support solution page.

With the updated files, images at the end of a TIFF file can be read in the same amount of time as images at the beginning of the file. Note that a different syntax is necessary, so please read the instructions in the download carefully.

You should be aware that these updates will NOT be included in the upcoming R2008b release. These changes were implemented very recently, and the relevant development deadline for R2008b changes was a couple of months ago.

Also, we don't have any updates available yet for improving the performance of creating such TIFF files using imwrite.


Steve Eddins manages the Image & Geospatial development team at The MathWorks and coauthored Digital Image Processing Using MATLAB. He writes here about image processing concepts, algorithm implementations, and MATLAB.

  • Assaf: Hi, I have 3 questions regarding the following sentence: “Rather than using the power spectrum from a...
  • Steve: Cara—I do not understand your question. Can you clarify it?
  • Cara Schiek: Hi. How could I do this same thing with image vales within the patches? cara
  • Steve: Vincent—OK, thanks for the information.
  • Vincent: My only data point on multithreading is that the Performance tab of the Task Manager shows increased CPU...
  • Steve: Vincent—Thanks for giving it a try and reporting back. I’m a bit skeptical that multithreading...
  • Vincent: Oops numbers were wrong. Data set was 450MB large so the numbers are: Results: ImageJ alone =< 5 s (or 90...
  • Vincent: Steve- I just had a quick run at the new imread.m patch. It’s much faster than the previous version...
  • Steve: Erik—Also, separability of the kernel provides no speed benefit in FFT-based implementations....
  • Steve: Erik—Good questions. Remember that, practically speaking, when we filter a 2-D signal with a 1-D filter...

These postings are the author's and don't necessarily represent the opinions of The MathWorks.

Related Topics