cv2.drawContours (): no rellenar los círculos dentro de los caracteres (Python, OpenCV)

Según lo sugerido por @Silencer, utilicé el código que publicó aquí para dibujar contornos alrededor de los números en mi imagen. En algún momento, trabajando con números como 0,6,8,9 , vi que sus contornos internos (los círculos) también se están rellenando. ¿Cómo puedo prevenir esto? ¿Hay un área de acción mín / máx para configurar para cv2.drawContours () para que pueda excluir el área interna?

ejemplo

Intenté pasar cv2.RETR_EXTERNAL pero con este parámetro solo se considera toda el área externa.

El código es este (nuevamente, gracias Silencer. Estuve buscando esto por meses …):

 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) 

La imagen principal en la que estoy trabajando.

    src

    Gracias

    ACTUALIZAR

    Implementé la respuesta Fiver a continuación y este es el resultado:

     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) 

    Para dibujar el char sin rellenar las regiones interiores cerradas:

    1. Encuentra los contornos en la imagen binaria trillada con jerarquía.

    2. encuentre los contornos externos que no tienen objetos internos (por jerarquía de bandera i ).

    3. Para cada contorno exterior:

      3.1 llénelo (quizás necesite verificar si es necesario);

      3.2 luego itere en sus contornos internos de los niños, luego rellénelos con otro color (como el color invertido).

    4. Combina con el código de recorte, recórtalos.

    5. Tal vez necesite ordenarlos, volver a llenarlos, normalizarlos.
    6. Tal vez, ahora puedes hacerlo con el modelo entrenado.

    FindContours, rellene las regiones cerradas interiores.

    introduzca la descripción de la imagen aquí

    Combine con esta respuesta Copie la forma en un canvas en blanco (OpenCV, Python) , haga más pasos, tal vez pueda obtener esto o mejor:

    introduzca la descripción de la imagen aquí


    El código principal para refill las regiones cerradas internas es el siguiente:

     #!/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) 

    Ejecuta este código con esta imagen:

    Conseguirás:

    introduzca la descripción de la imagen aquí


    Por supuesto, esto es para dos jerarquías. No he probado más de dos. Usted que necesita puede hacer la prueba por sí mismo.


    Actualizar:

    Observe en diferentes OpenCVs, los cv2.findContours devuelven valores diferentes. Para mantener el código ejecutable, solo podemos obtener los dos últimos valores devueltos: cnts, hiers = cv2.findContours (…) [- 2:]

    En OpenCV 3.4:

    introduzca la descripción de la imagen aquí

    En OpenCV 4.0:

    introduzca la descripción de la imagen aquí


    Dado que ya tiene una máscara de su paso de umbral, también puede usarla para bitwise_and contra el contorno dibujado:

     import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread('drawn_chars.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 c in contours: tmp_img = np.zeros(img_v.shape, dtype=np.uint8) cv2.drawContours(tmp_img, [c], -1, 255, cv2.FILLED) tmp_img = np.bitwise_and(tmp_img, ~img_v) plt.figure(figsize=(16, 2)) plt.imshow(tmp_img, cmap='gray') 

    He invertido la imagen para que los contornos sean blancos y dejé el recorte como ya lo resolvió. Aquí está el resultado en uno de los caracteres “O”:

    introduzca la descripción de la imagen aquí

    Código completo …

    Esto no ordenará las imágenes.

     import numpy as np import cv2 im = cv2.imread('imgs\\1.png') imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) ## Threshold ret, threshed = cv2.threshold(imgray, 127, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) ## FindContours image, cnts, hiers = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) canvas = np.zeros_like(im) n = len(cnts) hiers = hiers[0] for i, imgs in enumerate(cnts): cnt = cnts[i] res = cv2.drawContours(im, [cnt], 0, (0, 0, 0), -1) x, y, w, h = cv2.boundingRect(cnt) croped = res[y:y + h, x:x + w] if h > 10: cv2.imwrite("out\\croped{}.png".format(i), croped) cv2.imshow('i', croped) cv2.waitKey(0) for i, value in enumerate(cnts): ## this contour is a 3D numpy array cnt = cnts[i] res = cv2.drawContours(im, [cnt], 0, (0, 0, 0), -1) # cv2.imwrite("out\\contours{}.png".format(i), res) ## Find all inner contours and draw ch = hiers[i][2] while ch != -1: print(" {:02} {}".format(ch, hiers[ch])) res1 = cv2.drawContours(im, cnts, ch, (255, 255, 255), -1) ch = hiers[ch][0] x, y, w, h = cv2.boundingRect(cnt) croped = res[y:y + h, x:x + w] if h > 10: cv2.imwrite("out\\croped{}.png".format(i), croped) 

    Se acepta cualquier corrección.

    Esto hará definitivamente el trabajo …

     import cv2 import os import numpy as np img = cv2.imread("image.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) retval, thresholded = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) medianFiltered = cv2.medianBlur(thresholded, 3) _, contours, hierarchy = cv2.findContours(medianFiltered, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contour_list = [] for contour in contours: area = cv2.contourArea(contour) if area > 80: contour_list.append(contour) numbers = cv2.drawContours(img, contour_list, -1, (0, 0, 0), 2) cv2.imshow('i', numbers) cv2.waitKey(0) sorted_ctrs = sorted(contours, key=lambda ctr: cv2.boundingRect(ctr)[0]) for i, cnts in enumerate(contours): cnt = contours[i] x, y, w, h = cv2.boundingRect(cnt) croped = numbers[y:y + h, x:x + w] h, w = croped.shape[:2] print(h, w) if h > 15: cv2.imwrite("croped{}.png".format(i), croped) 

    Conceptualmente, esto es similar a la respuesta de Fivers, solo que bitwise_y ocurre fuera del bucle for y quizás sea mejor en términos de rendimiento. El código fuente está en C ++ para aquellos que buscan la respuesta de C ++ para este problema.

     int thWin = 3; int thOffset = 1; cv::adaptiveThreshold(image, th, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, thWin, thOffset); int minMoveCharCtrArea = 140; std::vector > contours; std::vector hierarchy; cv::findContours(th.clone(), contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); cv::Mat filtImg = cv::Mat::zeros(img.rows, img.cols, CV_8UC1 ); for (int i = 0; i< contours.size(); ++i) { int ctrArea = cv::contourArea(contours[i]); if (ctrArea > minMoveCharCtrArea) { cv::drawContours(filtImg, contours, i, 255, -1); } } cv::bitwise_and(th, filtImg, filtImg); 

    Recuerde clonar la imagen (para python debería ser copia) al pasar el argumento de la imagen de origen a findContours, ya que findContours modifica la imagen original. Reconozco que las versiones posteriores de opencv (quizás opencv3 +) no requieren clonación.