How would I detect horizontal-black lines using ImageMagick? - ruby

So I have what is essentially a spreadsheet in TIFF format. There is some uniformity to it...for example, all the column widths are the same. I want to de-limit this sheet by those known-column widths and basically create lots of little graphic files, one for each cell, and run OCR on them and store it into a database. The problem is that the horizontal lines are not all the same height, so I need to use some kind of graphics library command to check if every pixel across is the same color (i.e. black). And if so, then I know I've reached the height-delimiter for a cell. How would I go about doing that? (I'm using RMagick)

Use image#get_pixel: http://www.simplesystems.org/RMagick/doc/image2.html#get_pixels
Warning: Those docs are old, so it may have changed in the newer versions. Look at your own rdocs using $ gem server, assuming they have rdocs.
image#rows gives you the height of the image, then you can do something like (untested):
def black_line?(pixels)
pixels.each do |pixel|
unless pixel.red == 0 && pixel.green == 0 && pixel.blue == 0
return false
end
end
true
end
black_line_heights = []
height = image.rows
width = image.columns
height.times do |y|
pixels = image.get_pixel(0,y,width,1)
black_line_heights << y if black_line?(pixels)
end
Please keep in mind that I'm not sure about the api. Looking at older docs, and I can't test it now. But it looks like the general approach you would take. BTW, it assumes the row borders are 1 pixel thick. If not, change the 1 to the actual thickness and that might be enough to make it work like you expect.

Ehsanul had it almost right...the call is get_pixels, which takes in as arguments x,y,w,h and returns an array of those pixels. If the dimension is 1 thick, you'll get a nice one-d array.
Since the black in a document can vary, I altered Ehsanul's method a little bit to detect whether consecutive pixels were roughly the same color. AFter a 100 or so pixels, it's probably a line:
def solid_line?(pixels, opt={}, black_val = 10)
last_pixel = nil
thresh = opt[:threshold].blank? ? 4 : opt[:threshold]
pixels.each do |pix|
pixel = [pix.red, pix.green, pix.blue]
if last_pixel != nil
return false if pixel.reject{|p| (p-last_pixel[pixel.index(p)]).abs < thresh && p < black_val}.length > 0
end
last_pixel = pixel
end
true
end

Related

PIL: Imageobject.save() after drawing completely corrupts images and smurfs the ouput

I have these two functions in my program:
def depict_ph_increase(x,y,color, imobject):
program_print(color)
draw = PIL.ImageDraw.Draw(imobject)
draw.text((x, y),color,(255,255,255))
imobject.save('tmp-out.gif')
im_temp = PIL.Image.open("tmp-out.gif")#.convert2byte()
im_temp = im_temp.resize((930, 340), PIL.Image.ANTIALIAS)
MAP_temp = ImageTk.PhotoImage(im_temp)
map_display_temp = Label(main, image=MAP_temp)
map_display_temp.image = MAP_temp # keep a reference!
map_display_temp.grid(row=4,column=2, columnspan=3)
def read_temp_pixels(temperature_file, rngup, rngdown):
temp_image_object = PIL.Image.open(temperature_file)
(length, width) = get_image_size(temp_image_object)
(rngxleft, rngxright) = rngup
(rngyup,rngydown) = rngdown
print 'the length and width is'
print length, width
hotspots = 5;
for hotspot in range(0,hotspots):
color = "#ffffff"
while color == "#ffffff" or color == "#000000" or color == "#505050" or color == "#969696":
yc = random.randint(rngxleft, rngxright)
xc = random.randint(rngyup,rngydown)
color = convert_RGB_HEX(get_pixel_color(temp_image_object, xc, yc))
depict_ph_increase(xc,yc,color, temp_image_object)
The bottom one calls the top one. Their job is to read in this image:
It then randomly selects a few pixels, grabs their colors, and writes the hex values of the colors on top. But, when it redisplays the image, it gives me this garbage:
Those white numbers up near the upper right corner are the hex values its drawing. Its somehow reading the values from the corrupted image, despite the fact that I don't collect the values until AFTER I actually call the ImageDraw() method. Can someone explain to me why it is corrupting the image?
Some background--the get_pixel_color() function is used several other times in the program and is highly accurate, its just reading the pixel data from the newly corrupted image somehow. Furthermore, I do similar image reading (but not writing) at other points in my code.
If there is anything I can clarify, or any other part of my code you want to see, please let me know. You can also view the program in its entirety at my github here: https://github.com/jrfarah/coral/blob/master/src/realtime.py It should be commit #29.
Other SO questions I have examined, to no avail: Corrupted image is being saved with PIL
Any help would be greatly appreciated!
I fixed the problem by editing this line:
temp_image_object = PIL.Image.open(temperature_file)
to be
temp_image_object = PIL.Image.open(temperature_file).convert('RGB')

rmagick color out of range

I am trying to average the colours in an image, I have come up with the following script (from here):
scrip.rb:
require 'rmagick'
file = "./img.jpg"
img = Magick::Image.read(file).first
color = img.scale(1, 1).pixel_color(0,0)
p [color.red, color.green, color.blue]
img.jpg:
BUT the RGB output is [31829, 30571, 27931].
Questions:
Shouldn't the numbers be in the range of [0-255]?
What am I doing wrong?
What you have there is the red, green, and blue histogram values.
You need to divide by 256 in order to get each RGB value. In this case, the RGB values are:
require 'rmagick'
file = "./img.jpg"
img = Magick::Image.read(file).first
color = img.scale(1, 1).pixel_color(0,0)
p [color.red/256, color.green/256, color.blue/256]
# => [124, 119, 109]
This blog post provides a more thorough explanation on how to analyse images with RMagick.
The reason for the odd output is due to the bit depth. As a previous answer states, "They are stored in a 'quantum depth' of 16-bits." This was an initial assumption, but by looking at previous answers, this makes more sense. In order to properly convert these numbers back to your typically desires [0-255] range, you must divide the values by 256.
Note: You can change the quantum depth during runtime. When reading the file, you should be able to use a block like shown in the following code.
img = Magick::Image.read(file){self.depth = 8}.first

Importing Stack of Images

So I have the code to import a stack of images, but I am getting an error: Subscripted assignment dimension mismatch.
myPath = 'E:\folder name\'; %'
fileNames = dir(fullfile(myPath, '*.tif'));
width = 1400;
height = 1050;
nbImages = length(fileNames);
C=uint8(zeros(width, height, nbImages));
for i=1:length(fileNames)
C(:,:,i)=imread(cat(2,'E:\folder name\',fileNames(i).name));
i
end
I understand that the error is originating from the for loop, but I don't know of any other way to fill in an empty matrix with images.
Your images must not be all the same size. You can handle this by using explicit assignment for the first two dimensions. This will zero-pad any images which are smaller than the rest.
im = imread(...);
C(1:size(im, 1), 1:size(im, 2), i) = im;
Also, there is a good chance that your images have multiple color channels (the third dimension), so you'll likely want to concatenate along the fourth dimension rather than the third.
C(:,:,:,i) = imread(...)
Obviously it all depends what you want to do with the images, but in general, if you want a "stack" of images (or a "stack" of anything, really), then it sounds like you should be collecting them as a cell array instead.
Also, the correct way to create safe filenames is using the fullfile command
e.g.
C = cell(1, length(nbImages));
for i = 1 : length (fileNames)
C{i} = imread (fullfile ('E:','folder name', fileNames(i).name));
end
If you really want to concatenate to a 3D matrix from your cell array, assuming you have checked this is possible, you can do this very easily using comma-separated-list generator syntax:
My3DMatrix = cat(3, C{:});

Change Range Of Displayed Pixel Values from Command Line?

I'm working with some MRI data in Matlab 2014b, but the data is formed of intensity values not RGB. To get around this I use the code below to form a movie out of the MRI frames (I'm working on dynamic data here)
My problem is that the images need to have altered display values for the pixels, as the default only displays between -Inf and Inf, and I need between 0 and 0.25 to get a sensible image out of my data.
Are there any ways to pass that change from the script in to the movie, and then to write to file? I can only seem to do this per image in implay, and I'd like an automated way to edit each image and then store as a frame for a movie..?
%Code for producing movie.
graymap = gray(256);
for i = 1:32
a(:,:,i) = cmunique(Reformed_Data_Colourmap(:,:,i));
end
for i = 1:32
b = im2frame(a(:,:,i),graymap);
a(:,:,1) = ((b.cdata));
image(a(:,:,1))
colormap 'gray'
%The change needs to be here, to display pixel values from 0 to 0.25, to allow for a sensible image from the MR data.
frames(1,i) = getframe;
end
movie(frames)
The solution is provided:
for i = 1:32
b = im2frame(a(:,:,i),graymap);
a(:,:,1) = ((b.cdata));
clims = [0 250];
%image(a(:,:,1),clims)
colormap 'gray'
imagesc(a(:,:,1),clims);
%set('window', [0 400])
frames(1,i) = getframe;
end
clims solves the issue.

First pixel of the image

I just scanned a sample image and I'm trying to detect the first pixel which has a value of "0" in the binary-image.
I used paint to write a text and when i used the following prog, it always catches the bottom most pixel.
clear all;
x=imread('textjay.png');
y=im2bw(x);
height=size(y,1); % row
width=size(y,2); % col
valueoftheindex=0;
pixel_value=0;
for i=1:width
for j=1:height
pixel_value=y(j,i);
if (pixel_value==0)
valueofthewidth=i;
valueofthehieght=j;
break
end
end
end
valueofthewidth
valueofthehieght
imtool(y)
This depends a lot on what you consider to be "the first pixel".
Assuming that you can live with the pixel order that MATLAB assigns you could use
idx = find(y == 0, 1, 'first')
[row_idx, col_idx] = ind2sub(size(y), idx)
For other measures of "firstness" you would have to either transpose the input matrix (the image), or resort to some more refined way of calculation.
And yes, the break will only break the inner loop, as dawe already pointed out.

Resources