04/12/2017 Numérisation
In [25]:
%matplotlib notebook
from skimage import io
import [Link] as plt
from skimage import color
import numpy as np
im = [Link]('[Link]')
im = color.rgb2grey(im)*255
hist = [Link](im, bins=[Link](0, 256))
fig, axes = [Link](1, 2, figsize=(8, 3))
axes[0].imshow(im, cmap=[Link], interpolation='nearest')
axes[0].axis('off')
axes[1].plot(hist[1][:-1], hist[0], lw=2)
axes[1].set_title('histogram of grey values')
Out[25]:
Text(0.5,1,'histogram of grey values')
Choix du seuillage de l'image.
[Link] 1/22
04/12/2017 Numérisation
In [26]:
from skimage import filters
fig, axes = [Link](1, 2, figsize=(8, 3), sharey=True)
val = filters.threshold_otsu(im)
print(val)
axes[0].imshow(im > val, cmap=[Link], interpolation='nearest')
axes[0].set_title('Seuillage ' + str(val))
axes[1].imshow(im > 170, cmap=[Link], interpolation='nearest')
axes[1].set_title('Seuillage 170')
for a in axes:
[Link]('off')
a.set_adjustable('box-forced')
im_clean=im > val
plt.tight_layout()
146.11715332
Top Down algorithme
Projection X et Y.
[Link] 2/22
04/12/2017 Numérisation
In [27]:
xcut = [Link](im_clean, axis=0)
ycut = [Link](im_clean, axis=1)
fig, axes = [Link](2, 2, figsize=(8, 3))
axes[0,0].plot(xcut, lw=1)
axes[1,0].plot(ycut, lw=1)
from [Link] import medfilt
from scipy import ndimage
from [Link] import find_peaks_cwt
def cut(im, axis):
cut = [Link](im, axis=axis)
#cut = medfilt(cut)
#cut = ndimage.gaussian_filter(cut, sigma=3)
return (find_peaks_cwt(cut, [Link](1,10)), cut)
def xCut(im):
return cut(im, 0)
def yCut(im):
return cut(im, 1)
_,xcut = xCut(im_clean)
_,ycut = yCut(im_clean)
axes[0,1].plot(xcut, lw=1)
axes[1,1].plot(ycut, lw=1)
Out[27]:
[<[Link].Line2D at 0x7f4baefc7898>]
Chercher les pics de notre courbe
[Link] 3/22
04/12/2017 Numérisation
In [28]:
from [Link] import line
from numpy import copy
xpeak = find_peaks_cwt(xcut, [Link](1,10))
xmin = min(xcut)
ypeak = find_peaks_cwt(ycut, [Link](1,10))
ymin = min(ycut)
fig, axes = [Link](2, 2, figsize=(8, 3))
axes[0,0].plot(xcut, lw=1)
for x in xpeak:
axes[0,0].plot([x,x], [xmin,xcut[x]], 'k-')
axes[1,0].plot(ycut, lw=1)
for y in ypeak:
axes[1,0].plot([y,y], [ymin,ycut[y]], 'k-')
im_cloneX = [Link](im_clean)
for x in xpeak:
rr,cc = line(0,x,im_clean.shape[0]-1,x)
im_cloneX[rr,cc] = 0
im_cloneY = [Link](im_clean)
for y in ypeak:
rr,cc = line(y,0,y,im_clean.shape[1]-1)
im_cloneY[rr,cc] = 0
axes[0,1].imshow(im_cloneX, cmap="gray")
axes[1,1].imshow(im_cloneY, cmap="gray")
Out[28]:
<[Link] at 0x7f4baee797b8>
On cherche les fenêtres des pics et on prend le pic qui a la fenêtre la plus grande (plus grand niveau de
blanc).
[Link] 4/22
04/12/2017 Numérisation
[Link] 5/22
04/12/2017 Numérisation
In [29]:
def condition(v, d):
return v/d < 1.005 and d/v < 1.005
def get_window(v, cut):
start = v
end = v
value = cut[v]
while start > 0 and condition(value, cut[start]):
start -= 1
while end < len(cut) - 1 and condition(value, cut[end]):
end += 1
return (start, end)
def best_value(peak, cut):
best = -1
dist = -1
for p in peak:
(start, end) = get_window(p, cut)
if end - start > dist:
best = p
dist = end - start
if dist < 4:
return -1
return best
def best_value_x(im):
peak, cut = xCut(im)
return best_value(peak, cut)
def best_value_y(im):
peak, cut = yCut(im)
return best_value(peak, cut)
fig, axes = [Link](2, 2, figsize=(8, 6))
axes[0,0].plot(xcut, lw=1)
axes[0,0].set_title('window x')
for x in xpeak:
(start, end) = get_window(x, xcut)
axes[0,0].plot([start,start], [xmin,xcut[start]], 'k-')
axes[0,0].plot([end,end], [xmin,xcut[end]], 'k-')
axes[0,1].plot(xcut, lw=1)
axes[0,1].set_title('meilleur pic x')
best_x_peak = best_value_x(im_clean)
(start, end) = get_window(best_x_peak, xcut)
axes[0,1].plot([start,start], [xmin,xcut[start]], 'k-')
axes[0,1].plot([end,end], [xmin,xcut[end]], 'k-')
[Link] 6/22
04/12/2017 Numérisation
axes[1,0].plot(ycut, lw=1)
axes[1,0].set_title('window y')
for y in ypeak:
(start, end) = get_window(y, ycut)
axes[1,0].plot([start,start], [ymin,ycut[start]], 'k-')
axes[1,0].plot([end,end], [ymin,ycut[end]], 'k-')
axes[1,1].plot(ycut, lw=1)
axes[1,1].set_title('meilleur pic y')
best_y_peak = best_value_y(im_clean)
(start, end) = get_window(best_y_peak, ycut)
axes[1,1].plot([start,start], [ymin,ycut[start]], 'k-')
axes[1,1].plot([end,end], [ymin,ycut[end]], 'k-')
Out[29]:
[<[Link].Line2D at 0x7f4baeba8c18>]
Nous allons maintenant utiliser l'approche complète XY cut, par subdivision en multiple blocs. L'approche
récursive est ici utilisée.
Les blocs blancs sont supprimés avec la fonction isWhite. On utilise un nombre maximum de niveau de
récursivité en fonction du niveau de subdivision recherché. Nous recherchons ici la division en paragraphe,
en ligne de texte voire en mots.
[Link] 7/22
04/12/2017 Numérisation
In [30]:
from [Link] import variance
from [Link] import mean
MAX_LEVEL = 20
class Node:
[Link] 8/22
04/12/2017 Numérisation
def __init__(self, im, left=None, right=None):
[Link] = left
[Link] = right
self.start_cut_x = 0
self.size_x = len(im)
self.start_cut_y = 0
self.size_y = 0
if self.size_x != 0:
self.size_y = len(im[0])
def is_white(im):
if len(im) == 0 or len(im)*len(im[0]) < 10:
return True
v = variance(im)
return v < 0.01
def horizontal_cut(im, level):
if is_white(im):
return None
if level > MAX_LEVEL:
return Node(im)
# On récupère la position de notre meilleur pic
c = best_value_y(im)
if c != -1:
# On crée deux images
left_im = im[:c-1, :]
right_im = im[c:, :]
# On subdivise ces 2 images suivant l'axe opposé
left_node = vertical_cut(left_im, level + 1)
if left_node:
left_node.size_x = c
right_node = vertical_cut(right_im, level + 1)
if right_node:
right_node.start_cut_x = c
return Node(im, left_node, right_node)
return vertical_cut(im, level+1)
def vertical_cut(im, level):
if is_white(im):
return None
if level > MAX_LEVEL:
return Node(im)
# On récupère la position de notre meilleur pic
c = best_value_x(im)
if c != -1:
# On crée deux images
left_im = im[:, :c-1]
right_im = im[:, c:]
# On subdivise ces 2 images suivant l'axe opposé
left_node = horizontal_cut(left_im, level + 1)
[Link] 9/22
04/12/2017 Numérisation
if left_node:
left_node.size_y = c
right_node = horizontal_cut(right_im, level + 1)
if right_node:
right_node.start_cut_y = c
return Node(im, left_node, right_node)
return horizontal_cut(im, level + 1)
def display_rec(im, node, x0=0, y0=0):
# Affichage des blocs sur l'image
x = x0 + node.start_cut_x
y = y0 + node.start_cut_y
if [Link] or [Link]:
if [Link]:
display_rec(im, [Link], x, y)
if [Link]:
display_rec(im, [Link], x, y)
else:
end_x = x+node.size_x
end_y = y+node.size_y
if end_x == len(im):
end_x -= 1
if end_y == len(im[0]):
end_y -= 1
# Afficher le bloc
if display_filled:
im[x:end_x, y:end_y] = 0
else:
im[x:end_x, y] = 0
im[x:end_x, end_y] = 0
im[x, y:end_y] = 0
im[end_x, y:end_y] = 0
display_filled = True
node = horizontal_cut(im_clean, 0)
fig, axes = [Link](2, 1, figsize=(8, 6))
im_clone = [Link](im_clean)
im_white = [Link]((len(im_clone), len(im_clone[0]))) * 255
display_rec(im_white, node)
axes[0].imshow(im_white, cmap="gray")
axes[0].set_title('xy_cut filled')
display_filled = False
im_clone = [Link](im_clean)
display_rec(im_clone, node)
axes[1].imshow(im_clone, cmap="gray")
axes[1].set_title('xy_cut rec')
[Link] 10/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: divide by zero encountered in long_scalars
Out[30]:
Text(0.5,1,'xy_cut rec')
On applique RLSA en utilisant un outil morphologique (ici l'élément est une ligne de 2 pixels).
[Link] 11/22
04/12/2017 Numérisation
In [31]:
im_rlsa = [Link](im_white)
el = [Link]([[1, 1]])
im_rlsa = (im_rlsa == 0) * 1
result = [Link](ndimage.binary_closing(im_rlsa, structure=el).astype(im_rlsa.d
type))
fig, axes = [Link](1, 1, figsize=(8, 6))
[Link](result, cmap="gray")
axes.set_title('xy_cut filled')
Out[31]:
Text(0.5,1,'xy_cut filled')
Approche bottom up en utilisant la detection de contours. Seul l'enveloppe rectangulaire est ici affichée.
[Link] 12/22
04/12/2017 Numérisation
In [32]:
def find_min_max_axis(points, axis):
if len(points) == 0:
retunr (0,0)
minA = points[0,axis]
maxA = points[0,axis]
for v in points[:,axis]:
if v < minA:
minA = v
if v > maxA:
maxA = v
return (minA, maxA)
def contour_to_rect(contour):
return (find_min_max_axis(contour,0), find_min_max_axis(contour,1))
from skimage import measure
contours = measure.find_contours(im_clean, 0)
im_clone = [Link](im_clean)
for n, contour in enumerate(contours):
rec = contour_to_rect(contour)
x = int(rec[0][0])
end_x = int(rec[0][1])
y = int(rec[1][0])
end_y = int(rec[1][1])
im_clone[x:end_x, y:end_y] = 0
fig, ax = [Link]()
[Link](im_clone, cmap=[Link])
[Link]('image')
ax.set_xticks([])
ax.set_yticks([])
[Link]()
[Link] 13/22
04/12/2017 Numérisation
Nous ajoutons maitenant du bruit sur notre image de référence. On remarque que des nouveaux éléments
apparaissent, pouvant même créer de nouveaux blocs si le bruit est important. De facon générale, les blocs
correspondent au texte.
[Link] 14/22
04/12/2017 Numérisation
In [33]:
def poivre_sel(image):
row,col,ch = [Link]
s_vs_p = 0.5
amount = 0.004
out = [Link](image)
# Salt mode
num_salt = [Link](amount * [Link] * s_vs_p)
coords = [[Link](0, i - 1, int(num_salt))
for i in [Link]]
out[coords] = 1
# Pepper mode
num_pepper = [Link](amount* [Link] * (1. - s_vs_p))
coords = [[Link](0, i - 1, int(num_pepper))
for i in [Link]]
out[coords] = 0
return out
im = [Link]('[Link]')
im = poivre_sel(im)
im_g = color.rgb2grey(im)*255
val = filters.threshold_otsu(im_g)
im_clean = (im_g > val)*1
fig, axes = [Link](2, 1, figsize=(8, 6))
axes[0].imshow(im_g, cmap=[Link])
display_filled = True
MAX_LEVEL = 20
node = horizontal_cut(im_clean, 0)
im_clone = [Link](im_clean)
im_white = [Link]((len(im_clone), len(im_clone[0]))) * 255
display_rec(im_white, node)
axes[1].imshow(im_white, cmap="gray")
axes[1].set_title('xy_cut filled')
[Link] 15/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: invalid value encountered in long_scalars
Out[33]:
Text(0.5,1,'xy_cut filled')
Nous allons maitenant tenter de detecter les différents éléments. On estime qu'un bloc de texte est
représenté en majorité avec du blanc.
Prenons pour cela le cas extrait d'un journal. On utilise par exemple la méthode top down pour retrouver les
blocs.
[Link] 16/22
04/12/2017 Numérisation
In [34]:
from skimage import io
import [Link] as plt
from skimage import color
import numpy as np
im2 = [Link]('[Link]')
im2_g = color.rgb2grey(im2)*255
val = filters.threshold_otsu(im2_g)
im2_clean = (im2_g < val)*1
fig, axes = [Link](2, 2, figsize=(8, 6))
axes[0,0].imshow(im2_g, cmap=[Link])
axes[0,1].imshow(im2_clean, cmap=[Link])
display_filled = True
MAX_LEVEL = 20
node = horizontal_cut(im2_clean, 0)
im2_clone = [Link](im2_clean)
im2_white = [Link]((len(im2_clone), len(im2_clone[0]))) * 255
display_rec(im2_white, node)
axes[1,0].imshow(im2_white, cmap="gray")
axes[1,0].set_title('xy_cut filled')
MAX_LEVEL = 30
node1 = horizontal_cut(im2_clean, 0)
im2_clone = [Link](im2_clean)
im2_white = [Link]((len(im2_clone), len(im2_clone[0]))) * 255
display_rec(im2_white, node1)
axes[1,1].imshow(im2_white, cmap="gray")
axes[1,1].set_title('xy_cut filled')
[Link] 17/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: invalid value encountered in long_scalars
Out[34]:
Text(0.5,1,'xy_cut filled')
Les images sont en gris et le texte en blanc. On différencie les images du texte en fonction de la moyenne de
noir. Une image est en général, un amas noir, donc une moyenne inférieur à 0.5.
[Link] 18/22
04/12/2017 Numérisation
In [35]:
from [Link] import variance
def display_rec_color(im, node, x0=0, y0=0):
x = x0 + node.start_cut_x
y = y0 + node.start_cut_y
if [Link] or [Link]:
if [Link]:
display_rec_color(im, [Link], x, y)
if [Link]:
display_rec_color(im, [Link], x, y)
else:
end_x = x+node.size_x
end_y = y+node.size_y
if end_x == len(im):
end_x -= 1
if end_y == len(im[0]):
end_y -= 1
rec = im[x:end_x, y:end_y]
v = mean(rec)
if (v < 0.6):
im[x:end_x, y:end_y] = 150
else:
im[x:end_x, y:end_y] = 255
im2_clean = (im2_g < val)*1
display_rec_color(im2_clean, node, True)
fig, axes = [Link](1, 1, figsize=(8, 6))
[Link](im2_clean,cmap="gray")
[Link] 19/22
04/12/2017 Numérisation
Out[35]:
<[Link] at 0x7f4baf285588>
Nous créons une fonction chargée de detecter les zones d'image et de texte qui prend en paramètre le
chemin. Il faudra faire attention car certaines images ont les niveaux de gris inversés. Il suffit de detecter si le
niveau de noir est plus important que le niveau de blanc, en réalisant la moyenne de l'image en niveau de
gris puis en inversant la binarisation.
[Link] 20/22
04/12/2017 Numérisation
In [22]:
MAX_LEVEL = 15
def detect_image_text(path):
from skimage import io
import [Link] as plt
from skimage import color
import numpy as np
im2 = [Link](path)
im2_g = color.rgb2grey(im2)*255
val = filters.threshold_otsu(im2_g)
if mean(im2_g) < 125:
im2_clean = (im2_g < val)*1
else:
im2_clean = (im2_g > val)*1
node = horizontal_cut(im2_clean, 0)
im2_clone = [Link](im2_clean)
display_rec_color(im2_clone, node, True)
fig, axes = [Link](2, 1, figsize=(8, 6))
axes[0].imshow(im2_clean, cmap="gray")
axes[1].imshow(im2_clone, cmap="gray")
detect_image_text('[Link]')
[Link] 21/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/scipy/signal/_pe
ak_finding.py:412: RuntimeWarning: divide by zero encountered in dou
ble_scalars
snr = abs(cwt[line[0][0], line[1][0]] / noises[line[1][0]])
/home/ronan-j/anaconda3/lib/python3.6/site-packages/scipy/signal/_pe
ak_finding.py:412: RuntimeWarning: invalid value encountered in doub
le_scalars
snr = abs(cwt[line[0][0], line[1][0]] / noises[line[1][0]])
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
[Link]: RuntimeWarning: invalid value encountered in long_scalars
[Link] 22/22