2

I want to extract handwritten characters that are written into boxes like this. Form field

I am extracting squares of a width of 29 pixels, which is giving me images like these.

Extracted images 1 Extracted Images 2 Extracted Images 3

To correctly recognize chars, the individual character images need to be extremely clean. Like this,

Clean chars 1 Clean chars 2

What am I doing is,

  1. Compute the horizontal and vertical projection of every image.
  2. Iterate through each element of both arrays. If the value of projection is greater than certain threshold, that mean it hasn't encountered the border. It removes whitespace around the border.

  3. Then find contours in the image.

  4. If the area of contour is greater than some threshold. Get the bounding rectangle and crop it.

But the problem is, the method is not that accurate. In some cases it works fine, but in most cases if fails miserably. It produces images like,

enter image description here enter image description here

Also projection values are very specific to this image (or images closer to this image). It doesn't generalize well.

Is there any other method that can work well for this situation?

The code,

char = cv2.imread(image)
char_gray = cv2.cvtColor(char, cv2.COLOR_BGR2GRAY)
char_bw = cv2.adaptiveThreshold(char_gray, 255, 
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 9)

(rows, cols) = char_gray.shape

bit_not = cv2.bitwise_not(char_bw)
proj_h = cv2.reduce(bit_nv2.REDUCE_AVG)

proj_v = cv2.reduce(bit_not, 0, cv2.REDUCE_AVG)

thresh_h = 200
thresh_v = 100

start_x, start_y, end_x, end_y = 0, 0, cols - 1, rows - 1
#proj_h = proj_h[0]
proj_v = proj_v[0]

num_iter_h = cols // 8
num_iter_v = rows // 8

for _ in range(num_iter_h):
    if proj_h[start_y][0] > 35:
        start_y += 1

for _ in range(num_iter_h):
    if proj_h[end_y][0] > 160:
        end_y -= 1

for _ in range(num_iter_v):
    if proj_v[start_x] > 15: #25:
        start_x += 1

for _ in range(num_iter_v):
    if proj_v[end_x] > 125:
        end_x -= 1

print('processing.. %s.png' % idx)
output_char = char[start_y:end_y, start_x:end_x]
output_char = get_cropped_char(output_char)
return output_char


def get_cropped_char(img):
    """
    Returns Grayscale cropped image
    """

img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

blur = cv2.GaussianBlur(img, (3,3), 0)

thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 75, 10)
im2, cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

contour = None
for c in cnts:
    area = cv2.contourArea(c)
    if area > 100:
        contour = c
if contour is None: return None
(x, y, w, h) = cv2.boundingRect(contour)
img = img[y:y+h, x:x+w]
return img
Arka
  • 1,073
  • 1
  • 10
  • 14

2 Answers2

2

I don't think it's a good method to directly crop the char after threshold the image. I believe morphy-op can make scene.

The block elements is arrange tidily, so try morphy-erode-op to seperate the blockes (or remove the blockes borders). Once you get the clean chars-image, you can be easily crop the char images.

...

Poor english, 哈哈哈


This is result I get.

Croped images.

enter image description here

The steps:

enter image description here

Kinght 金
  • 17,681
  • 4
  • 60
  • 74
  • Are you talking about erode operation? – Arka Oct 04 '17 at 12:34
  • 1
    Wow. That is exactly I want to do. But, after eroding. How do you separate the boxes? – Arka Oct 04 '17 at 13:33
  • 1
    The second row is erode-op, the third row is threshold-op. Then do findContours and filter them by width/height/area and so on, you can get the separate boundingbox(as show in -2th row). Use cv2.rectangle to draw filled rect like the fouth row on the black canvas, you can get the mask. Next, use this make to operation on gray, you can get the clean char images. – Kinght 金 Oct 04 '17 at 13:55
2

I am new to OpenCV (and I am working on similar project...), but here is what I can say from experience. Extract clean chars is possible, at least for the last two. The first one is a little bit more difficult due to the line which cross the number.

You have to make gray version of the image, threshold and try some opening/closing operations. After that you have to do morphological transformation to remove the horizontal/vertical lines of each square. I tried with my version of the program and it do the job for a 40%. I need to improve it...

After that, with the result, you have to extract the bounding box of each number. It's not difficult. Some numbers will fail, but most of them will be extracted. "Extremely clean" is pretty hard to get at this level.

Do more research. There are plenty of examples of how to do most the of the operations..

EDIT: you must have the image similar to mine. Working on something like this is more easier..

example

This is what I achieved from mine: look at the inner squares, each around the single number. They can be easily extracted and saved for next processing.

example2

lucians
  • 2,239
  • 5
  • 36
  • 64
  • I am also pretty new to OpenCV. I am battling with these problems for the last few weeks. As the results are not consistent, I have at least 5 approaches to every single step. In your case, are you first removing the horizontal/vertical lines using opening/closing, then detecting the contours, and then the bounding area of the detected contours? – Arka Oct 04 '17 at 11:57
  • Well, I am using OpenCV and Python for the last 3 months... The process I use is near the one you described, operation more or less. – lucians Oct 04 '17 at 12:14
  • How accurately did you able to remove boundaries in your image? – Arka Oct 04 '17 at 12:33
  • In the images I posted, the program only "select" them, not removing. I tried with the image you posted above and the same script gives error. Please provide a better image and I will run it through the program. If it works I will post the code, if not then nothing. Understand me, it's no more so _open_ as before.. – lucians Oct 04 '17 at 12:50
  • How are you filtering the bounding boxes of the characters? How are you able to tell the characters and the boxes apart? I am interested to know. – Arka Oct 27 '17 at 06:56
  • I'm not telling the boxes apart from characters. I am extracting them.. Search for extract ROI. – lucians Oct 27 '17 at 06:58
  • But when you extract you may get a part of the surrounding boxes other than the character. How are you handling that situation? – Arka Oct 27 '17 at 07:07
  • If lines (vertical or horizontal) are included in extracted ROI, I can try to remove using more filters (open-close more than others) and morphological lines detection. Take a look at my 2 questions [here](https://stackoverflow.com/questions/46472713/improve-houghlines-for-horizontal-lines-detect-python-opencv) and [here](https://stackoverflow.com/questions/46281623/delete-segmented-lines-opencv-python). – lucians Oct 27 '17 at 07:40