Search code examples
imagematlabimage-processingline

Draw a horizontal line on an image whose row corresponds to the largest count of dark pixels


I wish to draw a horizontal line on a binary image in MATLAB which has the maximum amount of black pixels. So in this word for example:

... I have to identify the horizontal line with most pixels.

I understand that I can open the binary image variable editor, and plot a row which has maximum zeroes however that doesn't seem to work.

I have to make a baseline of this word like this:

... as the output assuming that the maximum pixels lie where I drew the line.


Solution

  • Purely going by your definition, you want to figure out the row that has the largest amount of black pixels. Simply sum over all columns of each row and find the maximum. Then when you're done, locate the row with the largest count and set this line to red.

    Something like this comes to mind. I'm going to read your image from StackOverflow directly and am going to use the image processing toolbox to help me with this analysis:

    %// Read image from SO
    im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
    
    %// Keep a copy of a binary version
    im_bw = im2bw(im);
    
    %// Sum over all of the columns and look for dark pixels
    row_sums = sum(~im_bw, 2);
    
    %// Find row with max sum
    [~,row_max] = max(row_sums);
    
    %// Draw a red line through the original image as it's in RGB
    im(row_max,:,1) = 255;
    im(row_max,:,2:3) = 0;
    
    %// Show the image
    imshow(im);
    

    The first image reads in the image directly from SO and puts it into the MATLAB workspace. The next line thresholds the image to binary to allow the analysis to be easier. We also keep a copy of the original image so we can mark the baseline with red. The next line after that uses sum and sums over every column of each row individually and adds up the black pixels. This is achieved by inverting the binary image so that dark pixels become bright to facilitate the summing. We then use max to figure out the row with the largest sum and that's done by looking at the second output of max. Once we find this location, we use this row and set all of the pixels in this row to red, or RGB = (255,0,0).

    I get this image:

    enter image description here

    Now the above code draws a line from the left to the right. If you wanted to limit this and only draw a red line where there is text, perhaps find the left most and right most dark pixel and add a bit of breathing room to them, then draw a line through.... something like this comes to mind:

    %// Read image from SO
    im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
    
    %// Keep a copy of a binary version
    im_bw = im2bw(im);
    
    %// Sum over all of the columns and look for dark pixels
    row_sums = sum(~im_bw, 2);
    
    %// Find row with max sum
    [~,row_max] = max(row_sums);
    
    %// Find left most and right most black columns
    [~,left_most] = find(~im_bw,1,'first');
    [~,right_most] = find(~im_bw,1,'last');
    
    %// Buffer for drawing the line before the first and after the last column
    buf = 20;
    
    %// Draw the line
    im(row_max,left_most-buf:right_most+buf,1) = 255;
    im(row_max,left_most-buf:right_most+buf,2:3) = 0;
    
    %// Show the image
    imshow(im);
    

    As you can see, most of the code remains the same (reading in the image, thresholding, column summing and max) but towards the end, I use find to find the first and last instance of a black pixel with respect to the columns. I then use these columns with the same maximum row found earlier, then subtracting the left most black column pixel location and adding the right most black column pixel location by a buffer amount (I chose 20 here), then setting the line of pixels within this region to red.

    We get:

    enter image description here


    Now, it is your wish to find the row with the second highest sum and draw another line through that row. That's not bad to do. However, this will require some post-processing because the outer edges of each character is not a single pixel thick... so the second highest sum may actually still give you a row that is around the first maximum. As such, I would suggest shrinking the text slightly then applying the row sum logic again. You can do this with morphological binary erosion with a small structuring element... say, a 3 x 3 square. This can be done with imerode for the erosion and using strel to specify the square structuring element.

    You'd apply the row sum logic to this new image, but then use these results and draw on the original image. You don't want to operate on this new image because it looks like there are some areas of the text that are a single pixel thick and these will be eliminated after erosion.

    Something like this comes to mind:

    %// Read image from SO
    im = imread('http://s15.postimg.org/cwg2sxnwr/mathworksss.png');
    
    %// Keep a copy of a binary version - also invert for ease
    im_bw = ~im2bw(im);
    
    %// Slightly erode the text
    im_bw = imerode(im_bw, strel('square', 3));
    
    %// Sum over all of the columns and look for dark pixels
    row_sums = sum(im_bw, 2);
    
    %// Sort the column sums in descending order and figure out the two highest sums
    [~,ind_sort] = sort(row_sums,'descend');
    
    %// First highest sum is the bottom - mark as red
    red_row1 = ind_sort(1);
    
    %// Second highest sum is the middle - mark as red too
    red_row2 = ind_sort(2);
    
    %// Find left most and right most black columns
    [~,left_most] = find(im_bw,1,'first');
    [~,right_most] = find(im_bw,1,'last');
    
    %// Buffer for drawing the line before the first and after the last column
    buf = 20;
    
    %// Draw the two red lines
    im(red_row1,left_most-buf:right_most+buf,1) = 255;
    im(red_row1,left_most-buf:right_most+buf,2:3) = 0;
    
    im(red_row2,left_most-buf:right_most+buf,1) = 255;
    im(red_row2,left_most-buf:right_most+buf,2:3) = 0;
    
    %// Show the image
    imshow(im);
    

    As you can see, most of the logic is the same. The only thing I really changed was that I eroded the image, sorted the row sums in descending order and extracted the first two locations that are the highest. I then repeated the logic to draw a line through the row, but instead of one row, we have two now.


    We get:

    enter image description here