Numérisation
Numérisation
In [25]:
%matplotlib notebook
from skimage import io
import matplotlib.pyplot as plt
from skimage import color
import numpy as np
im = io.imread('paper.jpg')
im = color.rgb2grey(im)*255
hist = np.histogram(im, bins=np.arange(0, 256))
Out[25]:
Text(0.5,1,'histogram of grey values')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 1/22
04/12/2017 Numérisation
In [26]:
for a in axes:
a.axis('off')
a.set_adjustable('box-forced')
plt.tight_layout()
146.11715332
Projection X et Y.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 2/22
04/12/2017 Numérisation
In [27]:
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]:
[<matplotlib.lines.Line2D at 0x7f4baefc7898>]
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 3/22
04/12/2017 Numérisation
In [28]:
axes[1,0].plot(ycut, lw=1)
for y in ypeak:
axes[1,0].plot([y,y], [ymin,ycut[y]], 'k-')
im_cloneX = np.copy(im_clean)
for x in xpeak:
rr,cc = line(0,x,im_clean.shape[0]-1,x)
im_cloneX[rr,cc] = 0
im_cloneY = np.copy(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]:
<matplotlib.image.AxesImage 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).
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 4/22
04/12/2017 Numérisation
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 5/22
04/12/2017 Numérisation
In [29]:
for p in peak:
(start, end) = get_window(p, cut)
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)
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-')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 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]:
[<matplotlib.lines.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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 7/22
04/12/2017 Numérisation
In [30]:
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 8/22
04/12/2017 Numérisation
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
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 9/22
04/12/2017 Numérisation
if left_node:
left_node.size_y = c
if node.left or node.right:
if node.left:
display_rec(im, node.left, x, y)
if node.right:
display_rec(im, node.right, 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)
axes[0].imshow(im_white, cmap="gray")
axes[0].set_title('xy_cut filled')
display_filled = False
im_clone = np.copy(im_clean)
display_rec(im_clone, node)
axes[1].imshow(im_clone, cmap="gray")
axes[1].set_title('xy_cut rec')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 10/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: 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).
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 11/22
04/12/2017 Numérisation
In [31]:
im_rlsa = np.copy(im_white)
el = np.array([[1, 1]])
im_rlsa = (im_rlsa == 0) * 1
axes.imshow(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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 12/22
04/12/2017 Numérisation
In [32]:
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))
fig, ax = plt.subplots()
ax.imshow(im_clone, cmap=plt.cm.gray)
ax.axis('image')
ax.set_xticks([])
ax.set_yticks([])
plt.show()
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 14/22
04/12/2017 Numérisation
In [33]:
def poivre_sel(image):
row,col,ch = image.shape
s_vs_p = 0.5
amount = 0.004
out = np.copy(image)
# Salt mode
num_salt = np.ceil(amount * image.size * s_vs_p)
coords = [np.random.randint(0, i - 1, int(num_salt))
for i in image.shape]
out[coords] = 1
# Pepper mode
num_pepper = np.ceil(amount* image.size * (1. - s_vs_p))
coords = [np.random.randint(0, i - 1, int(num_pepper))
for i in image.shape]
out[coords] = 0
return out
im = io.imread('paper.jpg')
im = poivre_sel(im)
im_g = color.rgb2grey(im)*255
val = filters.threshold_otsu(im_g)
im_clean = (im_g > val)*1
axes[0].imshow(im_g, cmap=plt.cm.gray)
display_filled = True
MAX_LEVEL = 20
node = horizontal_cut(im_clean, 0)
im_clone = np.copy(im_clean)
im_white = np.ones((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')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 15/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: 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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 16/22
04/12/2017 Numérisation
In [34]:
im2 = io.imread('document.jpg')
im2_g = color.rgb2grey(im2)*255
val = filters.threshold_otsu(im2_g)
im2_clean = (im2_g < val)*1
axes[0,0].imshow(im2_g, cmap=plt.cm.gray)
axes[0,1].imshow(im2_clean, cmap=plt.cm.gray)
display_filled = True
MAX_LEVEL = 20
node = horizontal_cut(im2_clean, 0)
im2_clone = np.copy(im2_clean)
im2_white = np.ones((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 = np.copy(im2_clean)
im2_white = np.ones((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')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 17/22
04/12/2017 Numérisation
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: 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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 18/22
04/12/2017 Numérisation
In [35]:
if node.left or node.right:
if node.left:
display_rec_color(im, node.left, x, y)
if node.right:
display_rec_color(im, node.right, 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
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 19/22
04/12/2017 Numérisation
Out[35]:
<matplotlib.image.AxesImage 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.
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 20/22
04/12/2017 Numérisation
In [22]:
MAX_LEVEL = 15
def detect_image_text(path):
from skimage import io
import matplotlib.pyplot as plt
from skimage import color
import numpy as np
im2 = io.imread(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 = np.copy(im2_clean)
display_rec_color(im2_clone, node, True)
detect_image_text('sample.png')
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 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
er.py:2: RuntimeWarning: divide by zero encountered in long_scalars
/home/ronan-j/anaconda3/lib/python3.6/site-packages/ipykernel_launch
er.py:2: RuntimeWarning: invalid value encountered in long_scalars
http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 22/22