0% found this document useful (0 votes)
8 views22 pages

Numérisation

The document outlines a process for image digitization using Python libraries such as skimage and matplotlib. It details steps for image thresholding, peak detection, and recursive image cutting to identify text blocks. Additionally, it discusses morphological operations and contour detection for further image processing.

Uploaded by

darkysock
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views22 pages

Numérisation

The document outlines a process for image digitization using Python libraries such as skimage and matplotlib. It details steps for image thresholding, peak detection, and recursive image cutting to identify text blocks. Additionally, it discusses morphological operations and contour detection for further image processing.

Uploaded by

darkysock
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

04/12/2017 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))

fig, axes = plt.subplots(1, 2, figsize=(8, 3))


axes[0].imshow(im, cmap=plt.cm.gray, 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.

http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 1/22
04/12/2017 Numérisation

In [26]:

from skimage import filters

fig, axes = plt.subplots(1, 2, figsize=(8, 3), sharey=True)


val = filters.threshold_otsu(im)
print(val)

axes[0].imshow(im > val, cmap=plt.cm.gray, interpolation='nearest')


axes[0].set_title('Seuillage ' + str(val))

axes[1].imshow(im > 170, cmap=plt.cm.gray, interpolation='nearest')


axes[1].set_title('Seuillage 170')

for a in axes:
a.axis('off')
a.set_adjustable('box-forced')

im_clean=im > val

plt.tight_layout()

146.11715332

Top Down algorithme

Projection X et Y.

http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 2/22
04/12/2017 Numérisation

In [27]:

xcut = np.sum(im_clean, axis=0)


ycut = np.sum(im_clean, axis=1)

fig, axes = plt.subplots(2, 2, figsize=(8, 3))


axes[0,0].plot(xcut, lw=1)
axes[1,0].plot(ycut, lw=1)

from scipy.signal import medfilt


from scipy import ndimage
from scipy.signal import find_peaks_cwt

def cut(im, axis):


cut = np.sum(im, axis=axis)
#cut = medfilt(cut)
#cut = ndimage.gaussian_filter(cut, sigma=3)
return (find_peaks_cwt(cut, np.arange(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]:
[<matplotlib.lines.Line2D at 0x7f4baefc7898>]

Chercher les pics de notre courbe

http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 3/22
04/12/2017 Numérisation

In [28]:

from skimage.draw import line


from numpy import copy

xpeak = find_peaks_cwt(xcut, np.arange(1,10))


xmin = min(xcut)

ypeak = find_peaks_cwt(ycut, np.arange(1,10))


ymin = min(ycut)

fig, axes = plt.subplots(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 = 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]:

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 = plt.subplots(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-')

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]:

from scipy.ndimage.measurements import variance


from scipy.ndimage.measurements import mean
MAX_LEVEL = 20
class Node:

http://localhost:8888/nbconvert/html/Num%C3%A9risation.ipynb?download=false 8/22
04/12/2017 Numérisation

def __init__(self, im, left=None, right=None):


self.left = left
self.right = 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)

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

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 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)

fig, axes = plt.subplots(2, 1, figsize=(8, 6))


im_clone = np.copy(im_clean)

im_white = np.ones((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 = 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

result = np.array(ndimage.binary_closing(im_rlsa, structure=el).astype(im_rlsa.d


type))

fig, axes = plt.subplots(1, 1, figsize=(8, 6))

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]:

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 = np.copy(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 = 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

fig, axes = plt.subplots(2, 1, figsize=(8, 6))

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]:

from skimage import io


import matplotlib.pyplot as plt
from skimage import color
import numpy as np

im2 = io.imread('document.jpg')
im2_g = color.rgb2grey(im2)*255

val = filters.threshold_otsu(im2_g)
im2_clean = (im2_g < val)*1

fig, axes = plt.subplots(2, 2, figsize=(8, 6))

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]:

from scipy.ndimage.measurements 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 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

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 = plt.subplots(1, 1, figsize=(8, 6))


axes.imshow(im2_clean,cmap="gray")

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)

fig, axes = plt.subplots(2, 1, figsize=(8, 6))


axes[0].imshow(im2_clean, cmap="gray")
axes[1].imshow(im2_clone, cmap="gray")

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

You might also like