Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cv2.drawContours() - unfill circles inside characters (Python, OpenCV)

As suggested by @Silencer, I used the code he posted here to draw contours around the numbers in my image. At some point, working with numbers like 0,6,8,9 I saw that their inside contours (the circles) are being filled as well. How can I prevent this ? Is there a min/max area of action to set for cv2.drawContours() so I can exclude the inner area ?

example

I tried to pass cv2.RETR_EXTERNAL but with this parameter only the whole external area is considered.

The code is this (again, thanks Silencer. Was searching for this for months..):

import numpy as np
import cv2

im = cv2.imread('imgs\\2.png')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

#contours.sort(key=lambda x: int(x.split('.')[0]))

for i, cnts in enumerate(contours):
    ## this contour is a 3D numpy array
    cnt = contours[i]
    res = cv2.drawContours(im, [cnt], 0, (255, 0, 0), 1)
    cv2.imwrite("contours.png", res)
    '''
    ## Method 1: crop the region
    x,y,w,h = cv2.boundingRect(cnt)
    croped = res[y:y+h, x:x+w]
    cv2.imwrite("cnts\\croped{}.png".format(i), croped)
    '''
    ## Method 2: draw on blank
    # get the 0-indexed coords
    offset = cnt.min(axis=0)
    cnt = cnt - cnt.min(axis=0)
    max_xy = cnt.max(axis=0) + 1
    w, h = max_xy[0][0], max_xy[0][1]
    # draw on blank
    canvas = np.ones((h, w, 3), np.uint8) * 255
    cv2.drawContours(canvas, [cnt], -1, (0, 0, 0), -1)

    #if h > 15 and w < 60:
    cv2.imwrite("cnts\\canvas{}.png".format(i), canvas)

The main image on which I am working..

src

Thanks

UPDATE

I implemented Fiver answer below and this is the result:

import cv2
import numpy as np

img = cv2.imread('img.png')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
img_v = img_hsv[:, :, 2]

ret, thresh = cv2.threshold(~img_v, 127, 255, 0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for i, c in enumerate(contours):
    tmp_img = np.zeros(img_v.shape, dtype=np.uint8)
    res = cv2.drawContours(tmp_img, [c], -1, 255, cv2.FILLED)

    tmp_img = np.bitwise_and(tmp_img, ~img_v)

    ret, inverted = cv2.threshold(tmp_img, 127, 255, cv2.THRESH_BINARY_INV)

    cnt = contours[i]

    x, y, w, h = cv2.boundingRect(cnt)
    cropped = inverted[y:y + h, x:x + w]

    cv2.imwrite("roi{}.png".format(i), cropped)
like image 506
lucians Avatar asked Dec 19 '22 01:12

lucians


1 Answers

To draw the char without filled the closed inner regions:

  1. find the contours on the threshed binary image with hierarchy.

  2. find the outer contours that don't have inner objects (by flag hierarchyi).

  3. for each outer contour:

    3.1 fill it(maybe need check whether needed);

    3.2 then iterate in it's inner children contours, fill then with other color(such as inversed color).

  4. combine with the crop code, crop them.

  5. maybe you need sort them, resplit them, normalize them.
  6. maybe, now you can do ocr with the trained model.

FindContours, refill the inner closed regions.

enter image description here

Combine with this answer Copy shape to blank canvas (OpenCV, Python), do more steps, maybe you can get this or better:

enter image description here


The core code to refill the inner closed regions is as follow:

#!/usr/bin/python3
# 2018.01.14 09:48:15 CST
# 2018.01.15 17:56:32 CST
# 2018.01.15 20:52:42 CST

import numpy as np
import cv2

img = cv2.imread('img02.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

## Threshold 
ret, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

## FindContours
cnts, hiers = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]

canvas = np.zeros_like(img)
n = len(cnts)
hiers = hiers[0]

for i in range(n):
    if hiers[i][3] != -1:
        ## If is inside, the continue 
        continue
    ## draw 
    cv2.drawContours(canvas, cnts, i,  (0,255,0), -1, cv2.LINE_AA)

    ## Find all inner contours and draw 
    ch = hiers[i][2]
    while ch!=-1:
        print(" {:02} {}".format(ch, hiers[ch]))
        cv2.drawContours(canvas, cnts, ch, (255,0,255), -1, cv2.LINE_AA)
        ch = hiers[ch][0]

cv2.imwrite("001_res.png", canvas)

Run this code with this image:

You will get:

enter image description here


Of course, this is for two hierarchies. I haven't test for more than two. You who need can do test by yourself.


Update:

Notice in different OpenCVs, the cv2.findContours return different values. To keep code executable, we can just get the last two returned values use: cnts, hiers = cv2.findContours(...)[-2:]

In OpenCV 3.4:

enter image description here

In OpenCV 4.0:

enter image description here


like image 156
Kinght 金 Avatar answered Dec 27 '22 02:12

Kinght 金