2017-08-19 6 views
3

Je construis une application Web pour aider les élèves à apprendre les mathématiques.Détection de mots et de graphiques dans une image et découpe d'une image en 1 image par mot ou graphique

L'application doit afficher du contenu Maths provenant de fichiers LaTex. Ces fichiers Latex rendent (magnifiquement) en pdf que je peux convertir proprement en svg grâce à pdf2svg.

Le (svg ou png ou quel que soit le format d'image) l'image ressemble à ceci:

_______________________________________ 
|          | 
| 1. Word1 word2 word3 word4   | 
| a. Word5 word6 word7    | 
|          | 
| ///////////Graph1///////////  | 
|          | 
| b. Word8 word9 word10    | 
|          | 
| 2. Word11 word12 word13 word14  | 
|          | 
|_______________________________________| 

exemple réel:


Le but de l'application web est pour manipuler et ajouter du contenu à ceci, menant à quelque chose comme ceci:

_______________________________________ 
|          | 
| 1. Word1 word2      | <-- New line break 
|_______________________________________| 
|          | 
| -> NewContent1      | 
|_______________________________________| 
|          | 
| word3 word4       | 
|_______________________________________| 
|          | 
| -> NewContent2      | 
|_______________________________________| 
|          | 
| a. Word5 word6 word7    | 
|_______________________________________| 
|          | 
| ///////////Graph1///////////  | 
|_______________________________________| 
|          | 
| -> NewContent3      | 
|_______________________________________| 
|          | 
| b. Word8 word9 word10    | 
|_______________________________________| 
|          | 
| 2. Word11 word12 word13 word14  | 
|_______________________________________| 

Exemple:


Une seule grande image ne peut pas me donner la souplesse nécessaire pour faire ce genre de manipulations.

Mais si le fichier image était décomposé en fichiers plus petits contenant des mots simples et des graphiques simples, je pourrais faire ces manipulations.

Ce que je pense que je dois faire est de détecter les espaces dans l'image, et couper l'image en plusieurs sous-images, en regardant quelque chose comme ceci:

_______________________________________ 
|   |  |  |   | 
| 1. Word1 | word2 | word3 | word4  | 
|__________|_______|_______|____________| 
|    |  |     | 
| a. Word5 | word6 | word7   | 
|_____________|_______|_________________| 
|          | 
| ///////////Graph1///////////  | 
|_______________________________________| 
|    |  |     | 
| b. Word8 | word9 | word10   | 
|_____________|_______|_________________| 
|   |  |  |   | 
| 2. Word11 | word12 | word13 | word14 | 
|___________|________|________|_________| 

Je suis à la recherche d'une façon de faire . Selon vous, quelle est la voie à suivre?

Nous vous remercions de votre aide!

+0

verticale et projection horizontale. Première segment de l'image entière en rangées, puis chaque rangée en colonnes. –

+0

Merci Dan. J'ai l'idée. Quel outil utiliseriez-vous pour la projection verticale et horizontale?Peut-il être automatisé? Peut-il détecter les lignes et les colonnes? – enzolito

+0

Ce que vous faites est de calculer l'intensité moyenne par ligne (par exemple, en utilisant 'cv2.reduce'.) Utilisez-le pour identifier les espaces blancs entre les lignes.Trouver les points médians des espaces.Utilisez ceux comme des points de coupe pour générer un ensemble d'images, Un par ligne de texte/graphique Répétez maintenant la même chose par colonne –

Répondre

4

J'utiliserais une projection horizontale et verticale pour d'abord segmenter l'image en lignes, puis chaque ligne en plus petites tranches (par exemple des mots). Commencez par convertir l'image en niveaux de gris, puis inversez-la, afin que les espaces contiennent des zéros et que tout texte/graphique soit différent de zéro.

img = cv2.imread('article.png', cv2.IMREAD_COLOR) 
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
img_gray_inverted = 255 - img_gray 

Calcul projection horizontale - intensité moyenne par ligne, en utilisant cv2.reduce, et aplatir à un réseau linéaire.

row_means = cv2.reduce(img_gray_inverted, 1, cv2.REDUCE_AVG, dtype=cv2.CV_32F).flatten() 

Maintenant, trouvez les plages de lignes pour tous les espaces contigus. Vous pouvez utiliser la fonction fournie dans this answer.

row_gaps = zero_runs(row_means) 

Enfin Calculons les points médians des lacunes, que nous utiliserons pour couper l'image vers le haut.

row_cutpoints = (row_gaps[:,0] + row_gaps[:,1] - 1)/2 

Vous vous retrouvez avec quelque chose comme cette situation (les lacunes sont roses, rouges points de coupe):

Visualization of horizontal projection, gaps and cutpoints


L'étape suivante serait de traiter chaque ligne identifiée.

bounding_boxes = [] 
for n,(start,end) in enumerate(zip(row_cutpoints, row_cutpoints[1:])): 
    line = img[start:end] 
    line_gray_inverted = img_gray_inverted[start:end] 

Calcul de la projection verticale (intensité moyenne par colonne), trouver les lacunes et les points de coupe. De plus, calculez la taille des espaces pour filtrer les petits espaces entre les lettres individuelles.

column_means = cv2.reduce(line_gray_inverted, 0, cv2.REDUCE_AVG, dtype=cv2.CV_32F).flatten() 
column_gaps = zero_runs(column_means) 
column_gap_sizes = column_gaps[:,1] - column_gaps[:,0] 
column_cutpoints = (column_gaps[:,0] + column_gaps[:,1] - 1)/2 

Filtrer les points de coupure.

filtered_cutpoints = column_cutpoints[column_gap_sizes > 5] 

Et de créer une liste de boîtes de délimitation pour chaque segment.

for xstart,xend in zip(filtered_cutpoints, filtered_cutpoints[1:]): 
    bounding_boxes.append(((xstart, start), (xend, end))) 

Maintenant, vous vous retrouvez avec quelque chose comme ça (les lacunes sont encore rose, rouge des points de coupe):

Visualization of horizontal projection, gaps and cutpoints


Maintenant, vous pouvez découper l'image. Je vais visualiser les boîtes englobantes trouvées:

Visualization of bounding boxes


Le script complet:

import cv2 
import numpy as np 
import matplotlib.pyplot as plt 
from matplotlib import gridspec 


def plot_horizontal_projection(file_name, img, projection): 
    fig = plt.figure(1, figsize=(12,16)) 
    gs = gridspec.GridSpec(1, 2, width_ratios=[3,1]) 

    ax = plt.subplot(gs[0]) 
    im = ax.imshow(img, interpolation='nearest', aspect='auto') 
    ax.grid(which='major', alpha=0.5) 

    ax = plt.subplot(gs[1]) 
    ax.plot(projection, np.arange(img.shape[0]), 'm') 
    ax.grid(which='major', alpha=0.5) 
    plt.xlim([0.0, 255.0]) 
    plt.ylim([-0.5, img.shape[0] - 0.5]) 
    ax.invert_yaxis() 

    fig.suptitle("FOO", fontsize=16) 
    gs.tight_layout(fig, rect=[0, 0.03, 1, 0.97]) 

    fig.set_dpi(200) 

    fig.savefig(file_name, bbox_inches='tight', dpi=fig.dpi) 
    plt.clf() 

def plot_vertical_projection(file_name, img, projection): 
    fig = plt.figure(2, figsize=(12, 4)) 
    gs = gridspec.GridSpec(2, 1, height_ratios=[1,5]) 

    ax = plt.subplot(gs[0]) 
    im = ax.imshow(img, interpolation='nearest', aspect='auto') 
    ax.grid(which='major', alpha=0.5) 

    ax = plt.subplot(gs[1]) 
    ax.plot(np.arange(img.shape[1]), projection, 'm') 
    ax.grid(which='major', alpha=0.5) 
    plt.xlim([-0.5, img.shape[1] - 0.5]) 
    plt.ylim([0.0, 255.0]) 

    fig.suptitle("FOO", fontsize=16) 
    gs.tight_layout(fig, rect=[0, 0.03, 1, 0.97]) 

    fig.set_dpi(200) 

    fig.savefig(file_name, bbox_inches='tight', dpi=fig.dpi) 
    plt.clf() 

def visualize_hp(file_name, img, row_means, row_cutpoints): 
    row_highlight = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
    row_highlight[row_means == 0, :, :] = [255,191,191] 
    row_highlight[row_cutpoints, :, :] = [255,0,0] 
    plot_horizontal_projection(file_name, row_highlight, row_means) 

def visualize_vp(file_name, img, column_means, column_cutpoints): 
    col_highlight = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
    col_highlight[:, column_means == 0, :] = [255,191,191] 
    col_highlight[:, column_cutpoints, :] = [255,0,0] 
    plot_vertical_projection(file_name, col_highlight, column_means) 


# From https://stackoverflow.com/a/24892274/3962537 
def zero_runs(a): 
    # Create an array that is 1 where a is 0, and pad each end with an extra 0. 
    iszero = np.concatenate(([0], np.equal(a, 0).view(np.int8), [0])) 
    absdiff = np.abs(np.diff(iszero)) 
    # Runs start and end where absdiff is 1. 
    ranges = np.where(absdiff == 1)[0].reshape(-1, 2) 
    return ranges 


img = cv2.imread('article.png', cv2.IMREAD_COLOR) 
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
img_gray_inverted = 255 - img_gray 

row_means = cv2.reduce(img_gray_inverted, 1, cv2.REDUCE_AVG, dtype=cv2.CV_32F).flatten() 
row_gaps = zero_runs(row_means) 
row_cutpoints = (row_gaps[:,0] + row_gaps[:,1] - 1)/2 

visualize_hp("article_hp.png", img, row_means, row_cutpoints) 

bounding_boxes = [] 
for n,(start,end) in enumerate(zip(row_cutpoints, row_cutpoints[1:])): 
    line = img[start:end] 
    line_gray_inverted = img_gray_inverted[start:end] 

    column_means = cv2.reduce(line_gray_inverted, 0, cv2.REDUCE_AVG, dtype=cv2.CV_32F).flatten() 
    column_gaps = zero_runs(column_means) 
    column_gap_sizes = column_gaps[:,1] - column_gaps[:,0] 
    column_cutpoints = (column_gaps[:,0] + column_gaps[:,1] - 1)/2 

    filtered_cutpoints = column_cutpoints[column_gap_sizes > 5] 

    for xstart,xend in zip(filtered_cutpoints, filtered_cutpoints[1:]): 
     bounding_boxes.append(((xstart, start), (xend, end))) 

    visualize_vp("article_vp_%02d.png" % n, line, column_means, filtered_cutpoints) 

result = img.copy() 

for bounding_box in bounding_boxes: 
    cv2.rectangle(result, bounding_box[0], bounding_box[1], (255,0,0), 2) 

cv2.imwrite("article_boxes.png", result) 
+0

Merci Dan c'est plus que je ne pouvais même attendre! – enzolito

+0

OpenCV ne peut pas charger et écrire des fichiers .svg si je Est-ce qu'il y a un format d'image vectoriel que gère OpenCV? – enzolito

+0

Pour autant que je sache, [il ne peut pas] (https://github.com/opencv/opencv/tree/master/modules/imgcodecs/src.) Quand on y pense, à moins que vous ne le rendiez, ce ne sera pas une image raster, donc l'approche devrait probablement être différente. (TBH, je devrais faire quelques recherche pour vous donner une bonne réponse à cela) Bien qu'un pos La flexibilité vient à l'esprit, mais c'est juste une pensée rapide - rendre et trouver les limites en utilisant l'approche actuelle, puis utiliser les coordonnées pour trouver les pièces correspondantes du SVG. –

1

L'image est de qualité supérieure, parfaitement propre, non inclinée, caractères bien séparés. Un rêve ! Effectuez d'abord la binarisation et la détection de blob (standard dans OpenCV).

Ensuite, regroupez les caractères en regroupant ceux qui se chevauchent en ordonnées (c'est-à-dire en faisant face l'un à l'autre dans une rangée). Cela isolera naturellement les lignes individuelles.

Maintenant dans chaque rangée, trier les blobs de gauche à droite et regrouper par proximité pour isoler les mots. Ce sera une étape délicate, car l'espacement des caractères dans un mot est proche de l'espacement entre les mots distincts. Ne vous attendez pas à des résultats parfaits. Cela devrait fonctionner mieux qu'une projection.

La situation est pire avec italique que l'espacement horizontal est encore plus étroit. Vous devrez peut-être également regarder la "distance oblique", c'est-à-dire trouver les lignes qui tangent les caractères dans la direction des italiques. Ceci peut être réalisé en appliquant une transformée de cisaillement inverse.

enter image description here

Merci à la grille, les graphiques apparaissent comme de grandes taches.

+0

Merci Yves, je vais regarder dans ce – enzolito