Handwritten cross inside a square image recognition - python

this is a long one. I am currently (from way too much time) trying to differentiate between crossed out square, completely blacked out square, blank square, crossed out/blacked out circle and blank circle gather from a scanned image like the one you can see. My current approach (link to the repo here, the focus is on the function detect_answers in evaluator.py, sorry for the occasional italian comments/names) is:
Find the outer black borders;
Align the image to compensate for scanning misalignment;
Retrieve ID barcode;
Divide the whole image in small square such that each one contains either a circle or a square;
Classify each square based on the category mentioned above (crossed out square in green, completely blacked out square in blue, blank square can be left unprocessed, crossed out/blacked out circle in red and blank circle).
def detect_answers(bgr_image: np.array, bgr_img_for_debug: np.array,
x_cut_positions: List[int], y_cut_positions: Tuple[int],
is_60_question_sim, debug: str):
question_multiplier: int = 15 if is_60_question_sim else 20
letter: Tuple[str, ...] = ("L", "", "A", "B", "C", "D", "E")
user_answer_dict: Dict[int, str] = {i: "" for i in range(1, 61 - 20 * int(not is_60_question_sim))}
gr_image: np.array = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2GRAY)
# load SVM model
load_path = os.getcwd()
clf = load(os.path.join(load_path, "reduced.joblib"))
for y_index in range(len(y_cut_positions) - 1):
for x_index in range(len(x_cut_positions) - 1):
# if you are on a column with only numbers, skip it
if not (x_index - 1) % 7:
continue
x_top_left = int(not x_index % 7) * 7 + x_cut_positions[x_index]
x_bottom_right = int(not x_index % 7) * 2 + x_cut_positions[x_index + 1]
y_top_left: int = y_cut_positions[y_index]
y_bottom_right: int = y_cut_positions[y_index + 1]
crop_for_prediction: np.array = gr_image[y_top_left:y_bottom_right, x_top_left:x_bottom_right]
crop_for_prediction: np.array = cv2.resize(crop_for_prediction, (18, 18))
# category = ("QB", "QS", "QA", "CB", "CA")
# 0 1 2 3 4
crop_for_prediction: np.array = np.append(crop_for_prediction,
[x_index % 7, int(np.mean(crop_for_prediction))])
predicted_category_index: int = clf.predict([crop_for_prediction])[0]
return user_answer_dict
I am expecting to have a list with the position of each relevant coloured square and its category. Currently this is done via a trained model used previous manually labeled data (file can be found in the github repo). I have tried many different approach (mean of each square, counting black pixels, ...) but no method seems to be reliable enough to handle all the variation that handwriting provides. Furthermore, the algorithm should be quite fast since each time it runs it needs to evaluate 500-800 tests.
Sample input
Expected output
Correct Classification, ignore red numbers
WrongClassification
In particular, question 1D should be rounded by red contours (since it's a blacked out square) and almost everything from the last column didn't get classified correctly.
At your disposal for any question, clarification or discussion. Cheers

Related

Labeling a matrix

I've been trying to do a code that labels a binary matrix, i.e. I want to do a function that finds all connected components in an image and assigns a unique label to all points in the same component. The problem is that I found a function, imbinarize(), that creates a binary image and I want to know how to do it without that function (because I don't know how to do it).
EDIT: I realized that it isn't needed to binarize the image, because it is being assumed that all the images that are put as argument are already binarized. So, I changed my code. It happens that code is not working, and I think the problem is in one of the cycles, but I can't understand why.
import numpy as np
%matplotlib inline
from matplotlib import pyplot as plt
def connected_components(image):
M = image * 1
# write your code here
(row, column) = M.shape #shape of the matrix
#Second step
L = 2
#Third step
q = []
#Fourth step
#Method to look for ones starting on the pixel (0, 0) and going from left to right and top-down
for i in np.arange(row):
for j in np.arange(column):
if M[i][j] == 1:
M[i][j] = L
q.append(M[i-1][j])
q.append(M[i+1][j])
q.append(M[i][j-1])
q.append(M[i][j+1])
#Fifth step
while len(q) != 0: #same as saying 'while q is not empty'
if q[0] == 1:
M[0] = L
q.append(M[i-1][j])
q.append(M[i+1][j])
q.append(M[i][j-1])
q.append(M[i][j+1])
#Sixth step
L = L + 1
#Seventh step: goes to the beginning of the for-cycle
return labels
pyplot.binarize in its most simple form thresholds an image such that any intensity whose value is beyond a certain threshold is assigned a binary 1 / True and a binary 0 / False otherwise. It is actually more sophisticated than this as it uses some image morphology for noise removal as well as use adaptive thresholds to find the most optimal value to separate between foreground and background. As I see this post as more for validating the connected components algorithm you've created, I'm going to assume that the basic algorithm is fine and the actual algorithm to be out of scope for your needs.
Once you read in the image with matplotlib, it is most likely going to be three channels so you'll need to convert the image into grayscale first, then threshold after. We can make this more adaptive based on the number of channels that exist.
Therefore, let's define a function to threshold the image for us. You'll need to play around with the threshold until you get good results. Also take note that plt.imread reads in float32 values, so the threshold will be defined between [0-1]. We can try 0.5 as a good start:
def binarize(im, threshold=0.5):
if len(im.shape) == 3:
gray = 0.299*im[...,0] + 0.587*im[...,1] + 0.114*im[...,2]
else:
gray = im
return (gray >= threshold).astype(np.uint8)
This will check if the input image is in RGB. If it is, convert to grayscale accordingly. The method to convert from RGB to grayscale uses the SMPTE Rec. 709 standard. Once we have the grayscale image, simply return a new image where everything that meets the threshold and beyond gets assigned an integer 1 and everything else is integer 0. I've converted the result to an integer type because your connected components algorithm assumes a 0/1 labelling.
You can then replace your code with:
#First step
Image = plt.imread(image) #reads the image on the argument
M = binarize(Image) #imbinarize() converts an image to a binary matrix
(row, column) = np.M.shape #shape of the matrix
Minor Note
In your test code, you are supplying a test image directly whereas your actual code performs an imread operation. imread expects a string so by specifying the actual array, your code will produce an error. If you want to accommodate for both an array and a string, you should check to see if the input is a string vs. an array:
if type(image) is str:
Image = plt.imread(image) #reads the image on the argument
else:
Image = image
M = binarize(Image) #imbinarize() converts an image to a binary matrix
(row, column) = np.M.shape #shape of the matrix

python: continuous colour grading

I have a list of float values that run from negative to positive values. Feel free to use np.random.normal(0, 1, 1000) to randomly generate sample input (random numbers from a normal distribution of mean 0 and standard deviation 1). I'd like to map this array of numbers to the ColorBrewer RdBu red and blue diverging colour bar, such that the smallest number is the darkest blue (assume #053061), and the largest number is the darkest red (assume #b2182b).
I've looked at a similar question about colour gradation, which is close to my question, and in fact is kind of an answer. However, the answer requires generating an array of length, say, 500, that linearly maps between the starting and ending colour, and I would then have to bin my original input data to this colour_array. Surely there's some pre-built functionality somewhere out there that does this already? I may have missed out some kind of documentation somewhere.
In the question you posted, user #zelusp commented on the highest answer and provides a link to a page named Color gradients with Python from a guy named Ben Southgate.
It has a bunch of info on plotting between colors, I think this code from the page covers what you're asking? I think you would use linear_gradient() to make the list for you. The ColorBrewer link looks like the gradient passes through white on the way to the other color, so you could just make two gradients. I cludged on a normalize to the index range of the color gradients from this question.
def hex_to_RGB(hex):
''' "#FFFFFF" -> [255,255,255] '''
# Pass 16 to the integer function for change of base
return [int(hex[i:i+2], 16) for i in range(1,6,2)]
def RGB_to_hex(RGB):
''' [255,255,255] -> "#FFFFFF" '''
# Components need to be integers for hex to make sense
RGB = [int(x) for x in RGB]
return "#"+"".join(["0{0:x}".format(v) if v < 16 else
"{0:x}".format(v) for v in RGB])
def color_dict(gradient):
''' Takes in a list of RGB sub-lists and returns dictionary of
colors in RGB and hex form for use in a graphing function
defined later on '''
return {"hex":[RGB_to_hex(RGB) for RGB in gradient],
"r":[RGB[0] for RGB in gradient],
"g":[RGB[1] for RGB in gradient],
"b":[RGB[2] for RGB in gradient]}
def linear_gradient(start_hex, finish_hex="#FFFFFF", n=10):
''' returns a gradient list of (n) colors between
two hex colors. start_hex and finish_hex
should be the full six-digit color string,
inlcuding the number sign ("#FFFFFF") '''
# Starting and ending colors in RGB form
s = hex_to_RGB(start_hex)
f = hex_to_RGB(finish_hex)
# Initilize a list of the output colors with the starting color
RGB_list = [s]
# Calcuate a color at each evenly spaced value of t from 1 to n
for t in range(1, n):
# Interpolate RGB vector for color at the current value of t
curr_vector = [
int(s[j] + (float(t)/(n-1))*(f[j]-s[j]))
for j in range(3)
]
# Add it to our list of output colors
RGB_list.append(curr_vector)
return color_dict(RGB_list)
gradient1 = linear_gradient("#053061", "#FFFFFF", n=500):
gradient2 = linear_gradient("#FFFFFF", "#b2182b", n=500):
input = np.random.normal(0, 1, 1000)
old_min = min(input)
old_range = max(input) - old_min
new_min = 0
new_range = 999 + 0.9999999999 - new_min
indexable = [int((n - old_min) / old_range * new_range + new_min) for n in input]
for n in indexable:
if n < 500:
gradient1[n]
else:
gradient2[n]
Maybe something like that

local histogram equalization in matlab / python

i am very new in matlab. i want to write the code for local histogram equalization . i have been written code for global histogram equalization and i know that local equalization means do equalization for each part of image seperately but my question is that how i should choose this part of images ? for example should i do equalization for each 100 pixel that are neighbor separate of other pixels ? in the other word how i can take apart image to some parts and then do equalization to each part?
The most naive way to do what you ask is split up your image into non-overlapping blocks, do your global histogram code on that block and save it to the output. Suppose you defined the rows and columns of these non-overlapping blocks as the variables rows and cols. In your case, let's say it's 100 x 100, so rows = 100; cols = 100;. You would simply loop over each non-overlapping block, do your histogram equalization then set this to the same locations in the output.
Something like this below, assuming your image is stored in im:
rows = 100;
cols = 100;
out = zeros(size(im)); % Declare output variable
for ii = 1 : rows : size(im, 1)
for jj = 1 : cols : size(im, 2)
% Get the block
row_begin = ii;
row_end = min(size(im, 1), ii + rows);
col_begin = jj;
col_end = min(size(im, 2), jj + cols);
blk = im(row_begin : row_end, col_begin : col_end, :);
% Perform histogram equalization with the block stored in blk
% ...
% Assume the output of this is stored in O
out(row_begin : row_end, col_begin : col_end, :) = O;
end
end
Note the intricacy of the variable blk that stores the non-overlapping block. We let the beginning row and column simply be the loop counter ii and jj, but the ending row and column we must make sure that it's bounded by the dimensions of the image. That's why the min call is there. Otherwise, the ending row and column is simply the beginning row and column added by the size of the block in the corresponding dimensions. Also note that I've used : to index into the third dimension in case you have a colour image. Grayscale should not affect this code. You finally need to use the same indexing when storing the output in the output image. Note that I've assumed this is stored in the variable O which is the output of your customized histogram equalization function.
The output out will contain your locally histogram equalized image. Take note that you could theoretically do this in one line using blockproc in the image processing toolbox if you have it. This processes distinct blocks in your image and applies some function to it. Assuming your histogram equalization function is called hsteq, you would simply do this:
rows = 100; cols = 100;
out = blockproc(im, [rows, cols], #(s) hsteq(s.data));
The first input is the image you want to process, the second input defines the block size and finally the last element is the function you want to apply to each block. Note that blockproc supplies a customized structure into your function and so what is important is that you pull out the data field in the structure. This should produce the same output as the code above with loops.
We can use the tile-based local (adaptive) histogram equalization to implement AHE (as suggested in the other answer), but in that case we need to implement a bilinear interpolation-like technique to prevent sudden change of contrasts at the edges of the window, e.g., observe the equalized output below with python implementation of the same (here a 50x50 window is used for the tile):
def AHE(im, tile_x=8, tile_y=8):
h, w = im.shape
out = np.zeros(im.shape) # Declare output variable
for i in range(0, h, tile_x):
for j in range(0, w, tile_y):
# Get the block
blk = im[i: min(i + tile_x, h), j: min(j + tile_y, w)]
probs = get_distr(blk)
out[i: min(i + tile_x, h), j: min(j + tile_y, w)] = CHE(blk, probs)
return out
def CHE(im, probs):
T = np.array(list(map(int, 255*np.cumsum(probs))))
return T[im]
def get_distr(im):
hist, _ = np.histogram(im.flatten(),256,[0,256])
return hist / hist.sum()
We could instead implement the AHE algorithm from this thesis:
The implementation of algorithm yields better results (without the boundary artifacts):

Correct method and Python package that can find width of an image's feature

The input is a spectrum with colorful (sorry) vertical lines on a black background. Given the approximate x coordinate of that band (as marked by X), I want to find the width of that band.
I am unfamiliar with image processing. Please direct me to the correct method of image processing and a Python image processing package that can do the same.
I am thinking PIL, OpenCV gave me an impression of being overkill for this particular application.
What if I want to make this an expert system that can classify them in the future?
I'll give a complete minimal working example (as suggested by sega_sai). I don't have access to your original image, but you'll see it doesn't really matter! The peak distributions found by the code below are:
Mean values at: 26.2840960523 80.8255092125
import Image
from scipy import *
from scipy.optimize import leastsq
# Load the picture with PIL, process if needed
pic = asarray(Image.open("band2.png"))
# Average the pixel values along vertical axis
pic_avg = pic.mean(axis=2)
projection = pic_avg.sum(axis=0)
# Set the min value to zero for a nice fit
projection /= projection.mean()
projection -= projection.min()
# Fit function, two gaussians, adjust as needed
def fitfunc(p,x):
return p[0]*exp(-(x-p[1])**2/(2.0*p[2]**2)) + \
p[3]*exp(-(x-p[4])**2/(2.0*p[5]**2))
errfunc = lambda p, x, y: fitfunc(p,x)-y
# Use scipy to fit, p0 is inital guess
p0 = array([0,20,1,0,75,10])
X = xrange(len(projection))
p1, success = leastsq(errfunc, p0, args=(X,projection))
Y = fitfunc(p1,X)
# Output the result
print "Mean values at: ", p1[1], p1[4]
# Plot the result
from pylab import *
subplot(211)
imshow(pic)
subplot(223)
plot(projection)
subplot(224)
plot(X,Y,'r',lw=5)
show()
Below is a simple thresholding method to find the lines and their width, it should work quite reliably for any number of lines. The yellow and black image below was processed using this script, the red/black plot illustrates the found lines using parameters of threshold = 0.3, min_line_width = 5)
The script averages the rows of an image, and then determines the basic start and end positions of each line based on a threshold (which you can set between 0 and 1), and a minimum line width (in pixels). By using thresholding and minimum line width you can easily filter your input images to get the lines out of them. The first function find_lines returns all the lines in an image as a list of tuples containing the start, end, center, and width of each line. The second function find_closest_band_width is called with the specified x_position, and returns the width of the closest line to this position (assuming you want distance to centre for each line). As the lines are saturated (255 cut-off per channel), their cross-sections are not far from a uniform distribution, so I don't believe trying to fit any kind of distribution is really going to help too much, just unnecessarily complicates.
import Image, ImageStat
def find_lines(image_file, threshold, min_line_width):
im = Image.open(image_file)
width, height = im.size
hist = []
lines = []
start = end = 0
for x in xrange(width):
column = im.crop((x, 0, x + 1, height))
stat = ImageStat.Stat(column)
## normalises by 2 * 255 as in your example the colour is yellow
## if your images start using white lines change this to 3 * 255
hist.append(sum(stat.sum) / (height * 2 * 255))
for index, value in enumerate(hist):
if value > threshold and end >= start:
start = index
if value < threshold and end < start:
if index - start < min_line_width:
start = 0
else:
end = index
center = start + (end - start) / 2.0
width = end - start
lines.append((start, end, center, width))
return lines
def find_closest_band_width(x_position, lines):
distances = [((value[2] - x_position) ** 2) for value in lines]
index = distances.index(min(distances))
return lines[index][3]
## set your threshold, and min_line_width for finding lines
lines = find_lines("8IxWA_sample.png", 0.7, 4)
## sets x_position to 59th pixel
print 'width of nearest line:', find_closest_band_width(59, lines)
I don't think that you need anything fancy for you particular task.
I would just use PIL + scipy. That should be enough.
Because you essentially need to take your image, make a 1D-projection of it
and then fit a Gaussian or something like that to it. The information about the approximate location of the band should be used a first guess for the fitter.

My own OCR-program in Python

I am still a beginner but I want to write a character-recognition-program. This program isn't ready yet. And I edited a lot, therefor the comments may not match exactly. I will use the 8-connectivity for the connected component labeling.
from PIL import Image
import numpy as np
im = Image.open("D:\\Python26\\PYTHON-PROGRAMME\\bild_schrift.jpg")
w,h = im.size
w = int(w)
h = int(h)
#2D-Array for area
area = []
for x in range(w):
area.append([])
for y in range(h):
area[x].append(2) #number 0 is white, number 1 is black
#2D-Array for letter
letter = []
for x in range(50):
letter.append([])
for y in range(50):
letter[x].append(0)
#2D-Array for label
label = []
for x in range(50):
label.append([])
for y in range(50):
label[x].append(0)
#image to number conversion
pix = im.load()
threshold = 200
for x in range(w):
for y in range(h):
aaa = pix[x, y]
bbb = aaa[0] + aaa[1] + aaa[2] #total value
if bbb<=threshold:
area[x][y] = 1
if bbb>threshold:
area[x][y] = 0
np.set_printoptions(threshold='nan', linewidth=10)
#matrix transponation
ccc = np.array(area)
area = ccc.T #better solution?
#find all black pixel and set temporary label numbers
i=1
for x in range(40): # width (later)
for y in range(40): # heigth (later)
if area[x][y]==1:
letter[x][y]=1
label[x][y]=i
i += 1
#connected components labeling
for x in range(40): # width (later)
for y in range(40): # heigth (later)
if area[x][y]==1:
label[x][y]=i
#if pixel has neighbour:
if area[x][y+1]==1:
#pixel and neighbour get the lowest label
pass # tomorrows work
if area[x+1][y]==1:
#pixel and neighbour get the lowest label
pass # tomorrows work
#should i also compare pixel and left neighbour?
#find width of the letter
#find height of the letter
#find the middle of the letter
#middle = [width/2][height/2] #?
#divide letter into 30 parts --> 5 x 6 array
#model letter
#letter A-Z, a-z, 0-9 (maybe more)
#compare each of the 30 parts of the letter with all model letters
#make a weighting
#print(letter)
im.save("D:\\Python26\\PYTHON-PROGRAMME\\bild2.jpg")
print('done')
OCR is not an easy task indeed. That's why text CAPTCHAs still work :)
To talk only about the letter extraction and not the pattern recognition, the technique you are using to separate the letters is called Connected Component Labeling. Since you are asking for a more efficient way to do this, try to implement the two-pass algorithm that's described in this article. Another description can be found in the article Blob extraction.
EDIT: Here's the implementation for the algorithm that I have suggested:
import sys
from PIL import Image, ImageDraw
class Region():
def __init__(self, x, y):
self._pixels = [(x, y)]
self._min_x = x
self._max_x = x
self._min_y = y
self._max_y = y
def add(self, x, y):
self._pixels.append((x, y))
self._min_x = min(self._min_x, x)
self._max_x = max(self._max_x, x)
self._min_y = min(self._min_y, y)
self._max_y = max(self._max_y, y)
def box(self):
return [(self._min_x, self._min_y), (self._max_x, self._max_y)]
def find_regions(im):
width, height = im.size
regions = {}
pixel_region = [[0 for y in range(height)] for x in range(width)]
equivalences = {}
n_regions = 0
#first pass. find regions.
for x in xrange(width):
for y in xrange(height):
#look for a black pixel
if im.getpixel((x, y)) == (0, 0, 0, 255): #BLACK
# get the region number from north or west
# or create new region
region_n = pixel_region[x-1][y] if x > 0 else 0
region_w = pixel_region[x][y-1] if y > 0 else 0
max_region = max(region_n, region_w)
if max_region > 0:
#a neighbour already has a region
#new region is the smallest > 0
new_region = min(filter(lambda i: i > 0, (region_n, region_w)))
#update equivalences
if max_region > new_region:
if max_region in equivalences:
equivalences[max_region].add(new_region)
else:
equivalences[max_region] = set((new_region, ))
else:
n_regions += 1
new_region = n_regions
pixel_region[x][y] = new_region
#Scan image again, assigning all equivalent regions the same region value.
for x in xrange(width):
for y in xrange(height):
r = pixel_region[x][y]
if r > 0:
while r in equivalences:
r = min(equivalences[r])
if not r in regions:
regions[r] = Region(x, y)
else:
regions[r].add(x, y)
return list(regions.itervalues())
def main():
im = Image.open(r"c:\users\personal\py\ocr\test.png")
regions = find_regions(im)
draw = ImageDraw.Draw(im)
for r in regions:
draw.rectangle(r.box(), outline=(255, 0, 0))
del draw
#im.show()
output = file("output.png", "wb")
im.save(output)
output.close()
if __name__ == "__main__":
main()
It's not 100% perfect, but since you are doing this only for learning purposes, it may be a good starting point. With the bounding box of each character you can now use a neural network as others have suggested here.
OCR is very, very hard. Even with computer-generated characters, it's quite challenging if you don't know the font and font size in advance. Even if you're matching characters exactly, I would not call it a "beginning" programming project; it's quite subtle.
If you want to recognize scanned, or handwritten characters, that's even harder - you'll need to use advanced math, algorithms, and machine learning. There are quite a few books and thousands of articles written about this topic, so you don't need to reinvent the wheel.
I admire your effort, but I don't think you've gotten far enough to hit any of the actual difficulties yet. So far you're just randomly exploring pixels and copying them from one array to another. You haven't actually done any comparison yet, and I'm not sure the purpose of your "random walk".
Why random? Writing correct randomized algorithms is quite difficult. I would recommend starting with a deterministic algorithm first.
Why are you copying from one array to another? Why not just compare directly?
When you get the comparison, you'll have to deal with the fact that the image is not exactly the same as the "prototype", and it's not clear how you'll deal with that.
Based on the code you've written so far, though, I have an idea for you: try writing a program that finds its way through a "maze" in an image. The input would be the image, plus the start pixel and the goal pixel. The output is a path through the maze from the start to the goal. This is a much easier problem than OCR - solving mazes is something that computers are great for - but it's still fun and challenging.
Most OCR algorithms these days are based on neural network algorithms. Hopfield networks are a good place to start. Based on the Hopfield Model available here in C, I built a very basic image recognition algorithm in python similar to what you describe. I've posted the full source here. It's a toy project and not suitable for real OCR, but can get you started in the right direction.
The Hopfield model is used as an autoassociative memory to store and recall a set of bitmap images. Images are stored by calculating a corresponding weight matrix. Thereafter, starting from an arbitrary configuration, the memory will settle on exactly that stored image, which is nearest to the starting configuration in terms of Hamming distance. Thus given an incomplete or corrupted version of a stored image, the network is able to recall the corresponding original image.
A Java applet to toy with an example can be found here; the network is trained with example inputs for the digits 0-9. Draw in the box on the right, click test and see the results from the network.
Don't let the mathematical notation intimidate you, the algorithms are straightforward once you get to source code.
OCR is very, very difficult! What approach to use to attempt OCR will be based on what you are trying to accomplish (hand writing recongnition, computer generated text reading, etc.)
However, to get you started, read up on Neural Networks and OCR. Here are a few jump-right-in articles on the subject:
http://www.codeproject.com/KB/cs/neural_network_ocr.aspx
http://www.codeproject.com/KB/dotnet/simple_ocr.aspx
Use your favorite search engine to find information.
Have fun!

Categories