1

So this is basically something very simple, as in just get the horizontal projection plot and from that get the location of the lines on the image. But the problem is that the threshold that is applied is very variable. If I stay at a safe level, the correct number of lines are extracted whereas on the other hand unwanted results are extracted.

For example here is the image:

enter image description here

And its horizontal projection:

enter image description here

And here is the code I am using to extract the text lines:

%complementing as text must be non zero and background should be 0
img_comp = imcomplement(img);

%calculate the horizontal projections and plot it to verify the threshold
horizontal_projections = sum(img_comp, 2);
plot(horizontal_projections)

%A very crude method of automatically detecting the threshold

proj_mean = mean(horizontal_projections);
lines = horizontal_projections > floor(proj_mean); 

% Find Rising and falling edges
d = diff(lines);
startingColumns = find(d>0);
endingColumns = find(d<0);

% Extract each line and save it in a cell
for lines_k = 1 : length(startingColumns)
  lines_extracted{lines_k} = img(startingColumns(lines_k):endingColumns(lines_k), :);
end

I want to automate the threshold selection but am having trouble, if I use the threshold shown in my code that is the mean of the projections, it does extract 9 lines which are correct but the lines lose a lot of data as in:

enter image description here

This is the second line, the extenders and descenders of the letters have been cut off. Using the half of mean or third of it works but its different for every image and does not automate it at all.

StuckInPhDNoMore
  • 2,507
  • 4
  • 41
  • 73

2 Answers2

1

What about converting to YCbCr color space? Using the conversion formula from Wikipedia.

img = im2double(imread('StackOverflow-Example.jpg'));
rp = img(:, :, 1) / 255 ;
bp = img(:, :, 2) / 255 ;
gp = img(:, :, 3) / 255 ;
kb = 0.114;
kr = 0.299;
y = kr * rp + (1 - kr - kb) * gp + kb * bp;
y = max(max(y))-y;
y = y ./ y;
surf(y,'EdgeColor','none','LineStyle','none')
view(0, -90)

It looks like a good job of maintaining the information.

Edit:

I think you want each line

%% Load image and find intensity %%
img = im2double(imread('test.jpg')); % load image and convert to doubles to allow for calculations
rp = img(:, :, 1) / 255 ; % normalized red portion
bp = img(:, :, 2) / 255 ; % normalized blue portion
gp = img(:, :, 3) / 255 ; % normalized green portion
kb = 0.114; % blue constant from Wikipedia
kr = 0.299; % red constant from Wikipedia
x = kr * rp + (1 - kr - kb) * gp + kb * bp; % normalized intensity in image
x = max(max(x))-x; % removed background

y = x ./ x; % everything left is high

z = y;
z(isnan(y)) = 0; % turn nan's to zero
divisions = find(sum(z,2) > 5); % find all lines that have less than 5 pixels
divisions = [divisions(1); divisions(diff(divisions) > 10); size(z, 1)]; % find the line breaks

rows = cell(length(divisions), 1);

for i = 1:numel(rows)-1
    line = z(divisions(i):divisions(i+1), :); % grab line
    j = divisions(i) + find(sum(line,2) > 5) - 1; % remove the white space
    line = y(j, :);
    rows{i} = line; %store the line
end

rows(numel(rows)) = [];

%% plot each line %%
for i = 1:numel(rows) ; 
    figure(i) ; 
    surf(rows{i},'EdgeColor','none','LineStyle','none');
    view(0, -90) ;
end

%% plot entire page %%
figure(numel(rows) + 1)
surf(y,'EdgeColor','none','LineStyle','none') % plot of entire image
view(0, -90)

Edit: 2015/05/18 15:45 GMT

This has the values for the intensity left in:

img = im2double(imread('test.jpg'));
rp = img(:, :, 1) / 255 ;
bp = img(:, :, 2) / 255 ;
gp = img(:, :, 3) / 255 ;
kb = 0.114;
kr = 0.299;
x = kr * rp + (1 - kr - kb) * gp + kb * bp;
x = max(max(x))-x;
xp = x;
xp(xp == min(min(xp))) = nan;

y = x ./ x;

z = y;
z(isnan(y)) = 0;
divisions = find(sum(z,2) > 5);
divisions = [divisions(1); divisions(diff(divisions) > 10); size(z, 1)];

rows = cell(length(divisions) - 1, 1);

for i = 1:numel(rows)
    line = z(divisions(i):divisions(i+1), :);
    j = divisions(i) + find(sum(line,2) > 5) - 1;
    line = xp(j, :);
    rows{i} = line;

    figure(i) ; 
    surf(rows{i},'EdgeColor','none','LineStyle','none');
    axis('equal')
    view(0, -90) ;
end

figure(numel(rows) + 1)
surf(xp,'EdgeColor','none','LineStyle','none')
axis('equal')
view(0, -90)

Edit 2015-05-22 13:21 GMT

%Turn warning message off
warning('off', 'Images:initSize:adjustingMag');

%Read in image in int8
originalImg = imread('test.jpg');

%Convert to double
img = im2double(originalImg);

%Take R, G, & B components
rp = img(:, :, 1) ;
gp = img(:, :, 2) ;
bp = img(:, :, 3) ;

%Get intensity
kb = 0.114;
kr = 0.299;
yp = kr * rp + (1 - kr - kb) * gp + kb * bp;

%Flip to opposite of intensity
ypp = max(max(yp))-yp;

%Normalize flipped intensity
z = ypp ./ ypp;
z(isnan(z)) = 0;

%Find lines, this may need to be tuned
MaxPixelsPerLine = 5;
MinRowsPerLine = 10;
divisions = find(sum(z,2) > MaxPixelsPerLine);
divisions = [divisions(1); divisions(diff(divisions) > MinRowsPerLine); size(z, 1)];

%Preallocate for number of lines
colorRows = cell(length(divisions) - 1, 1);

for i = 1:numel(rows)
    %Extract the lines in RGB
    line = z(divisions(i):divisions(i+1), :);
    j = divisions(i) + find(sum(line,2) > 5) - 1;
    colorRows{i} = originalImg(j, :, :);

    %Print out the line
    figure(i) ;
    imshow(colorRows{i})
end

%Print out the oringinal image
figure(numel(rows) + 1)
imshow(originalImg)

%Turn the warning back on
warning('on', 'Images:initSize:adjustingMag');
user1543042
  • 3,422
  • 1
  • 17
  • 31
  • You could also try to increase the contrast. – mprat May 16 '15 at 20:36
  • Thank you. That seems to work perfectly. But I do not understand what is going on. Can you please comment your code a little bit. Also even though ``surf`` plots the lines individually, is there any way I can use this information to extract the original lines from the original image? Thanks – StuckInPhDNoMore May 17 '15 at 12:38
  • 1
    The individual lines are stored in rows. I also put in some comments. – user1543042 May 17 '15 at 16:11
  • Thank you, thats really helpful. Your implementation works very well with extracting the text lines, but the problem is that it the lines extracted are basically converted to NaN(or 0) and 1, in a way, logical format, thus losing all the information. Is there a way to get the lines from the original image, that is the extracted lines be in the colortype of the original image? even grayscale would, anything but logical. Thanks – StuckInPhDNoMore May 18 '15 at 12:08
  • Thank you. But the values are very small, They are no longer in their original format. Any way to get them back after the segmentation? – StuckInPhDNoMore May 21 '15 at 15:13
  • Do you mean back into RGB? – user1543042 May 21 '15 at 16:27
  • Yes please. I mean if I use ``imshow(rows{1})`` all I get is a black screen as the individual text pixels are very small and the rest is NaN – StuckInPhDNoMore May 22 '15 at 07:55
1

Short: graythresh(img) migth solve your problem

Longer:

With some morphological Methodes, you can extract the lines pretty easy. Small drawback though: they are somewhat out of order.

load your image

original = imread('o6WEN.jpg'); 

make it into a greyscale

img=rgb2gray(original); .

define a rectangular structuring element with about textheight and 'very' long

se = strel('rectangle',[30 200]); 

Filter it with a tophat filter. Long rectangular shapes with about textheight will be more prominent after this.

 img = imtophat(img,se);

Adjust the contrast:

img = imadjust(img);

define another structuring element, this time a line a bit shorter than textheight:

se = strel('line',20,0);

Dilate the picture with it to get rid of presisting gapps between letters

img = imdilate(img,se);

make image into black and with:

img=im2bw(img,graythresh(img));

use regionprops to get all the BoundingBoxes form your lines

 stats=regionprops(img,'BoundingBox');
 figure, imshow(img)

In Stats are now the Bounding Boxes from all your lines, sadly out of order. Maybe this could be corrected with BWlables or some sort of correlation. I just looked at the y-Koordinates of the BoundingBoxes and sorted accordingly.

BoundingBoxes=struct2cell(stats);
BoundingBoxes=cell2mat(BoundingBoxes'); % making it into an array
[~,ind]=sort(BoundingBoxes(:,2)); % sorting it to y
BoundingBoxes=BoundingBoxes(ind,:); % apply the sorted vector 

 lineNr=8;
imshow(original(BoundingBoxes(2,lineNr):BoundingBoxes(2,lineNr)+BoundingBoxes(4,lineNr),BoundingBoxes(1,lineNr):BoundingBoxes(1,lineNr)+BoundingBoxes(3,lineNr)  ))

hope that works for you

marco wassmer
  • 421
  • 2
  • 8
  • Thank you. Can you please comment on what is the purpose of ``lineNr``? and why is it set to 8? also in your code its giving error of ``index exceeds matrix dimensions`` in the last line of ``imshow``, Thanks – StuckInPhDNoMore May 17 '15 at 12:35
  • The last two lines are just to display some line , here Nr 8, and demonstrate how to get the imge of a singel line, you can delete it. – marco wassmer May 17 '15 at 13:46
  • Right, thanks. So where are the coordinates of each line? The order does not matter to me, so If I am to extract the individual lines from the original image, would I have to use the data in ``BoundingBoxes``? Thanks – StuckInPhDNoMore May 17 '15 at 14:05
  • Yes the data is in BoundingBoxes: first two values define the lower left corner, the second two the heigth and width. – marco wassmer May 17 '15 at 18:57