Search code examples
matlabimage-processingimage-segmentationscanning

Using Matlab to scan up from a specified row in an image to detect a new region based on intensity variation


Here is the type of images I'm working with:

enter image description here

You can see the pink line Ive plotted using the code below:

A=imread('b20.bmp');
AR=A(:,:,1);
[rows, columns] = size(AR);
y1 = 200;
y2 = 315;
row1 = AR(y1, :); % Extract this line of gray levels from the image.
figure, image(AR,'CDataMapping','scaled'); colormap('gray');
title('Input Image in Grayscale')
hold on; 
plot([0, columns], [y2, y2], 'm');

Im looking to scan upwards from the highlighted row (315) to the first row of the image in an effort to detect the dark region, once that region is detected I'm looking to plot another line at the midpoint of the dark region, similar to the first (the whole way across the image).

the reason I'm looking to do this is once the midpoints of the 2 regions are detected Im looking to obtain statistical information from the 2 lines such as standard deviations and means, in an attempt to process the 2 segments to give the surrounding rows or segments an overall average value.


Solution

  • Jonas pretty much told you how to solve it. However, because I like playing with images, I decided to write an answer. What I would do is extract out a sub-image that goes from rows 1 to 315, then I would independently find the average of each row. This would give you a 315 element vector... then from this result, whatever location gives us a huge spike, that's probably where the subdivision begins. Here's an artificial example. I tried recreating your's with some minimal code. I'm going to create a black and white image where the top half is black and the bottom half is white. Once I do that, I'll add some random Gaussian noise to it with an amplitude of 10, mean of 0 and standard deviation of 1. After this, I'll normalize the image so that it's between [0,1] in terms of the dynamic range:

    %// Set random seed generator
    rng(123);
    
    %// Create black and white image.  First 250 rows is black, next 250 rows is gray
    im = [zeros(250, 256); 128*ones(250,256)];
    
    %// Add Gaussian random noise of amplitude 10, mean 0, std.dev = 1
    im_noise = im + 10*randn(size(im));
    
    %// Normalize image so it's between [0,1]
    AR = (im_noise - min(im_noise(:))) / (max(im_noise(:)) - min(im_noise(:)));
    

    I've made the noisy image stored in AR so that you can copy and paste what I'm going to try next. For completeness, this is what the image looks like:

    enter image description here

    Now here's where the magic begins. Let's find the average of each row between rows 1 and 315:

    avgs = mean(AR(1:315,:), 2);
    

    The 2 parameter means to operate along the columns, which means that we will find the average of each row. This means that we will get a 315 element vector where each element is the average of a row.... so the first element is the average of row 1, second element is the average of row 2 and so on. If we plot these averages on a normal plot, where the row number is the horizontal axis and the mean intensity is on the vertical axis, this is what we get:

    plot(1:315, avgs);
    

    enter image description here

    As you can see, there's a clear spike at 250, and in the image I designed, right at row 250 is where I made the gray square appear. We can determine where this spike appears by doing diff combined with max where we compute pairwise distances between elements in an array. diff works where the first output is the second element minus the first, then the second element is the third pair minus second, and so on. In this case, because we're transitioning from low to high, this means that whatever location gives us the highest difference means that we took a number from the right (or the highest value of the edge) to the left (or the lowest value of the edge. Therefore, we find pairwise distances and determine the point where we have a pairwise distance that is the highest possible. So, do something like:

    [~,ind] = max(diff(avgs));
    

    What's nice about max here is that if there are multiple values that are seen in the data that share the same maximum, we only return the first occurring value. This is nice because once we detect our spike in the plot, we will find this result immediately. If you did this correctly, ind should be 250.

    One thing I'd like to note is that the above syntax is assuming you're transitioning from black to white. Should you have the opposite behaviour, you'll either want to look into use min, or if you want this to be agnostic of the type of gradient you are experiencing, use max combined with abs. That way, when we find the biggest difference, it'll either be a large positive number if it's going from black to white or a large negative number if it's going from white to black. If we remove all of the negative values with abs, this means that no matter what the sign is, we will see this as a large positive value and so we should be able to detect the jump regardless. Therefore, what you should do is:

    [~,ind] = max(abs(diff(avgs)));
    

    Now, all you have to do is show this image, then plot a line where this location is at:

    figure;
    imshow(AR);
    hold on;
    plot([1 size(AR,2)], [ind ind], 'r', 'LineWidth', 2);
    

    I've made the thickness of the line 2 pixels so you can see the line clearly, and I've made it in red. This is what we get:

    enter image description here


    Therefore, if you want to copy and paste this into MATLAB for you to run, here's the code:

    avgs = mean(AR(1:315,:), 2); 
    [~,ind] = max(abs(diff(avgs)));    
    figure;
    imshow(AR);
    hold on;
    plot([1 size(AR,2)], [ind ind], 'r', 'LineWidth', 2);