From 328b313a9fdb8fecbf49530260bda3753245e77c Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Sat, 2 Mar 2019 18:27:28 +0100 Subject: [PATCH] un cours de traitement d'image sans image --- 453/Cours/chap1.tex | 710 ++++++++++++++++++++++++++++++++++++++++++++ 453/Cours/harris.py | 45 +++ 453/Cours/hough.py | 40 +++ 453/Cours/main.tex | 29 ++ raccourcis.sty | 3 +- 5 files changed, 826 insertions(+), 1 deletion(-) create mode 100644 453/Cours/chap1.tex create mode 100644 453/Cours/harris.py create mode 100644 453/Cours/hough.py create mode 100644 453/Cours/main.tex diff --git a/453/Cours/chap1.tex b/453/Cours/chap1.tex new file mode 100644 index 0000000..74f3d4a --- /dev/null +++ b/453/Cours/chap1.tex @@ -0,0 +1,710 @@ +\documentclass[main.tex]{subfiles} +\begin{document} +\section{Introduction} +\subsection{Domaines d'applications} +Refaire le graphe stylé en TikZ +\emph{Whoua c'est vachement stylé le TI} +\subsection{Définion} + +\begin{defin} + \begin{itemize} + \item Une \emph{image} est une répresentaiton continue d'une fonction $f(x,y)$ qui relie $f$ à l'intensité lumineuse du point $(x,y)$. + \item Une \emph{Image numérique} Échantillonnage $I(x,y)$ dicret (Matrice 2D) de $f$ qui relie $I(x,y)$ à l'intensité lumineuse d'une case $(x,y)$ nommé \emph{pixel}. + \item Une Image numérique possède également une \emph{quantification}: nombre de bits pour décrire une couleur /intensité d'un pixel. + \end{itemize} +\end{defin} + +\subsection{Notation et structure} + +Pour accéder aux pixel d'une image: + +\begin{itemize} +\item $w$ : nombre de colonne $i\in[0,w-1]$ +\item $h$ : nombre de lignes $j\in [0,h-1]$ +\end{itemize} +Ainsi $I(i,j)$ représente la valeur du pixel à la $i$ème colonne et $j$ème ligne. + +Chaque pixel possède une valeur , quantifié qui peux correspondre à : +\begin{description} +\item[gris] intensité comme scalaire en $[0,2^n-1]$ où $n$ est la dynamique de l'image (la plupart du temps $n=8$) + +\item[couleur] Triplet $R,G,B$ , chaque canal correspond à un 'niveau de couleur' codé de meme manière que le gris. +\end{description} + +\subsection{Amélioration d'image} + +\paragraph{Objectifs} Réhausser le contraste d'une image pour faire apparaitre les objets distinctements. + +Pour cela on utilise des histogrammes + +\subsubsection{Histogrammes d'une image} + +Après quantification on peux compter les pixels de meme valeurs +\begin{defin} + \begin{itemize} + \item Un \emph{histogramme} est une application : + \[ + H: + \begin{cases} + [0,N-1] \to [0,wh]\\ + H(z) \mapsto card(\{(x,y)\in[0,w]\times [0,h] | I(x,y) = z \}) + \end{cases} + \] +\item On a ensuite \emph{l'histogramme normalisé} : +\[ + H_n: + \begin{cases} + [0,N-1] \to [01]\\ + H_n(z) \mapsto H(z)/(wh) + \end{cases} + \] + L'histogramme normalisé est une distribution de proba empirique. +\item Et \emph{l'histogramme cumulé normalisé} + \[ + H_{cn}: + \begin{cases} + [0,N-1] \to [0,1]\\ + H_{cn}(z) \mapsto\sum_{k=0}^{z} H_n(k) + \end{cases} + \] + L'histogramme cumulé normalisé est une fonction croissante et une focntion de répartition empirique. +\end{itemize} +\end{defin} + + +\begin{listing}[H] +\begin{minted}{C} + int H[N]; // histogramme +float Hn[N]; // histogramme cumulé +float Hcn[N]; // histogramme cumulé normalisé +for (i = 0; i & \\ + & + \end{bmatrix}}_{\text{ tenseur de structure}} + \vect{\Delta x \\ \Delta y} +\] +\end{prop} + +\begin{rem} +Le tenseur de structure est composé des dérivée de l'image sur le patch moyennée. Les vecteurs propres indiquent les direction principales de variation de gradient +dans le voisinage du point (voir l’ellipse du changement constant) +\end{rem} + +\paragraph{Méthode} : +On peux calculer $\lambda_1$ et $\lambda_2$ explicitement mais c'est lourd. On préfère utiliser : +\[ + R = \det(M) - \alpha tr(M)^2 = \lambda_1\lambda_2 -\alpha(\lambda_1+\lambda_2)^2 +\] + +Et on choisi $\alpha \in[0.04; 0.06]$. Les valeurs propres intéressante sont alors des maximums locaux de $R$. + +\paragraph{Algorithme} +\begin{itemize} +\item Calcul des gradients gaussien de l'image +\item Calcul du tenseur des structures +\item Calcul de $R$ +\item Seuillage et suppression des valeurs non maximales. +\end{itemize} +\begin{listing} + \inputminted{python}{harris.py} +\end{listing} + + +\paragraph{Comment gérer le changement d'échelle ?} Algorithme qui détecte en chaque endroit de l'image l'échelle qui ferait que cet endroit serait un coin. + + +\subsection{Détecteur FAST} +\subsection{Détecteur SIFT} + + + + + +\section{La couleur} + +\subsection{Les descripteurs} + +En niveaux de gris, le descripteur le plus simple est la luminosité. Un autre descripteur possible (Local Binary Pattern LBP) prend le voisinage 3x3 d'un pixel et fait un seuil des valeurs des pixels par rapport au pixel central. + +En couleurs, c'est plus complexe car il y a 3 canaux.\\ + +La couleur n'est pas forcément un atout car l'information supplémentaire n'est pas nécessairement un atout. En général, les algorithmes de traitement de couleurs sont construits pour des applications particulières : par exemple, suppression fond vert.\\ + +\paragraph{Capteurs RGB} on peut difficilement réaliser un système permettant de séparer les canaux pour les envoyer sur 3 capteurs différents. On réalise donc une matrice du type : + +\begin{center} +\begin{tabular}{cccccc} +R & V & R & V & R & V \\ +V & B & V & B & V & B \\ +R & V & R & V & R & V \\ +V & B & V & B & V & B \\ +\end{tabular} +\end{center} + +En 1 point donné, on interpole entre les données voisines pour les différents canaux. +Il y a plus de capteurs verts car c'est la couleur à laquelle l'oeil humain est le plus sensible.\\ + +Avantage : l'image RGB a donc la taille d'une image en niveaux de gris. \\ + +Inconvénient : sur les contours, on aura des aberrations à cause des interpolations. Par exemple, à la frontière entre une zone rouge et une zone verte, on fera apparaître du jaune. + +\paragraph{Sources lumineuses} on caractérise la lumière de manière spectrale, et on a don une grande variétés de spectres d'émission selon les sources. Donc en ne changeant ni l'objet, ni l'observateur, on peut avoir de grandes différences dans ce qui sera observé en fonction de l'éclairage "ambiant". + +\paragraph{Perception} l'important n'est pas ce qui compose l'environnement mais la manière dont on le perçoit. Par exemple : un damier sur laquelle une ombre est projetée, le cerveau humain sait que les carrés blancs sont toujours plus foncés que les gris, mais ce n'est pas forcément vrai entre un carré blanc "à l'ombre" et un carré gris dans la lumière. + +\paragraph{Descripteurs} principe de la trichromie : 3 couleurs primaires permettent de reproduire la quasi-totalité des couleurs + +\begin{itemize} +\item Observateur standard RGB 1931 : il existe une partie négative de la partie rouge, qui n'est pas cohérente avec la théorie additive. +\item Observateur standard CIE 1931 : + \[ \vect{ X \\ Y \\ Z } = \frac{1}{0.17697} + \begin{bmatrix} + 0.49 & 0.31 & 0.20\\ + 0.17697 & 0.81240 & 0.01063\\ + 0 & 0.01 & 0.99 + \end{bmatrix} + \vect{ R \\ G \\ B } \] +Le $Y$ est proche du $V$, le $Z$ est proche du $B$. +\end{itemize} + +\subsection{Seuillage optimal méthode de Otsu} + +la méthode d'Otsu est utilisée pour effectuer un seuillage automatique à partir de la forme de l'histogramme de l'image1, ou la réduction d'une image à niveaux de gris en une image binaire. L'algorithme suppose alors que l'image à binariser ne contient que deux classes de pixels puis calcule le seuil optimal qui sépare ces deux classes afin que leur variance intra-classe soit minimale. + +\begin{prop} + On cherche le seuil $t$ tel que : + \[ + \arg\max \omega_1(t)\omega_2(t)[\mu_1(t)-\mu_2(t)]^2 + \] + Avec: + \begin{itemize} + \item $\omega_1(t) = \sum_{i=0}^{t}H_n(i)$ + \item $\mu_1(t) = \frac{1}{\omega_1(t)}\sum_{i=0}^{t} i \cdot H_n(i)$. + \item idem pour $\omega_2$ et $\mu_2$. + \end{itemize} +\end{prop} +\section{Estimation} + +À partir d'une image on cherche à estimer un ensemble $\theta$ de paramètres (déplacememt, état d'un d'un système ) + +\paragraph{Source du problème} +On ne pas ignorer les outliers et déterminer les paramètre du modèle. Les methodes de tyoe moindres carré sont très sensibles aux outliers à cause de la fonction d'erreur quadratique associée. + +Différentes approches sont possibles: + +\subsection{Analyse de l'ensemble des résidus} + +On utilise des variation de la méthode des moindres carrés: + +\begin{prop}[Least Median of Squares] + On remplace la somme par la médiane : + \[ + \theta = \arg\min_{\theta}~\text{med } \rho(r_i) + \] +\end{prop} + +\begin{prop}[Least Trimmed Squares] + On tri les résidus et on en sélectionne $M$ où $N/2 < M< N$. + \[ + \theta \arg \min \sum_{i=0}^{M}\rho(r_i) + \] +\end{prop} + +Une autre alternative est d'utilisé une autre fonction de cout (cf UE 451),qui doit rester symétrique définie positive + +\subsection{RANSAC} +\begin{defin} +`` RANSAC, abréviation pour RANdom SAmple Consensus, est une méthode pour estimer les paramètres de certains modèles mathématiques. Plus précisément, c'est une méthode itérative utilisée lorsque l'ensemble de données observées peut contenir des valeurs aberrantes (outliers). Il s'agit d'un algorithme non-déterministe dans le sens où il produit un résultat correct avec une certaine probabilité seulement, celle-ci augmentant à mesure que le nombre d'itérations est grand. L'algorithme a été publié pour la première fois par Fischler et Bolles en 1981'' +\end{defin} + +\paragraph{Algorithme}~\\ +\textbf{entrées :}\vspace{-1em} +\begin{verbatim} + data - un ensemble d'observations + modele - un modèle qui peut être ajusté à des données + n - nb min de données nécessaires pour ajuster le modèle + k - nb max d'itérations de l'algorithme + t - seuil d'appartenance au modèle + d - seuil de données nécessaire pour valider le modèle +\end{verbatim} +\textbf{sorties :}\vspace{-1em} +\begin{verbatim} + meilleur_modèle - les paramètres du modèle qui correspondent le mieux aux données + meilleur_ensemble_points - données à partir desquelles ce modèle a été estimé + meilleure_erreur - l'erreur de ce modèle par rapport aux données +\end{verbatim} + +\begin{listing}[H] +\begin{verbatim} +itérateur := 0 +meilleur_modèle := aucun +meilleur_ensemble_points := aucun +meilleure_erreur := infini +tant que itérateur < k + points_aléatoires := n valeurs choisies au hasard à partir des données + modèle_possible := paramètres du modèle correspondant aux points_aléatoires + ensemble_points := points_aléatoires + + Pour chaque point des données pas dans points_aléatoires + si le point s'ajuste au modèle_possible avec une erreur inférieure à t + Ajouter un point à ensemble_points + + si le nombre d'éléments dans ensemble_points est > d + (ce qui implique que nous avons peut-être trouvé un bon modèle, + on teste maintenant dans quelle mesure il est correct) + modèle_possible := paramètres du modèle réajusté sur ensemble_points + erreur := ecart entre données et modèle. + si erreur < meilleure_erreur + (nous avons trouvé un modèle qui est mieux que tous les précédents, + le garder jusqu'à ce qu'un meilleur soit trouvé) + meilleur_modèle := modèle_possible + meilleur_ensemble_points := ensemble_points + meilleure_erreur := erreur + + incrémentation de l’itérateur + +retourne meilleur_modèle, meilleur_ensemble_points, meilleure_erreur +\end{verbatim} +\caption{Algorithme de la méthode randsac} +\end{listing} + + +\subsection{La segmentation} +\emph{poly de l'ENSTA très bien fait} + +La segmentation consiste à regrouper les différents pixels de l'image en un nombre (donné) de région (peut aussi consister a une taille de région maximale, les deux, etc...). En pratique on réalise une partition de l'image + + +\subsection{Segmentation par découpage} +\begin{defin} + L'idée des algorithmes de type « Split \& Merge » est de produire + automatiquement une partition initiale en régions petites (Split), qui vont ensuite croître en se regroupant (Merge) + \begin{itemize} + \item La partition initiale (Split) est réalisée en \emph{divisant récursivement} l'image en régions de tailles identiques lorsqu'un certain critère d'homogéneité n'est pas satisfait: \emph{$R$ est divisée} (ex: si $\sigma_R> seuil$) + + \item Lors de cette phase, le graphe d'adjacence, ou Region Adjacency Graph (RAG) est créé : à chaque région est associé un sommet du graphe, et des arêtes relient les sommets correspondants à deux régions qui se touchent. + \item La \emph{phase de regroupement} (Merge) utilise le RAG pour modifier la partition initiale : pour chaque sommet R du RAG, on cherche s'il existe un sommet $R'$ voisin dans le RAG et de valeur suffisamment proche, et si c'est le cas, on les fusionne: + $R et R'$ sont fusionnées si $\mu_{R}-\mu_{R'} < seuil$ +\end{itemize} +\end{defin} + + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "main" +%%% End: diff --git a/453/Cours/harris.py b/453/Cours/harris.py new file mode 100644 index 0000000..671cff3 --- /dev/null +++ b/453/Cours/harris.py @@ -0,0 +1,45 @@ +def harris(image, sigma=10, kappa=0.04): + derivees = gradient(image) + d_x = derivees[0] + d_y = derivees[1] + noyau = noyau_gaussien3(sigma) + #TODO: convol DoG + d_xx_lissee = ndimage.convolve(d_x * d_x,noyau , mode = 'nearest') + d_yx_lissee = ndimage.convolve(d_x * d_y,noyau , mode = 'nearest') + d_yy_lissee = ndimage.convolve(d_y * d_y,noyau , mode = 'nearest') + + determinant = d_xx_lissee * d_yy_lissee -d_yx_lissee * d_yx_lissee + trace = d_xx_lissee + d_yy_lissee + + image_harris = determinant - kappa * trace * trace + return image_harris + +def maxlocal(image_harris, seuil,size_patch=10): + """ Array*float -> Array """ + output = np.zeros(image_harris.shape) + voisinage = np.zeros((3,3)) + for ligne in range(size_patch,image_harris.shape[0]-size_patch): + for colonne in range(size_patch,image_harris.shape[1]-size_patch): + current_val = image_harris[ligne, colonne] + voisinage = image_harris[ligne - 1: ligne + 2, colonne - 1:colonne + 2].copy() + voisinage[(1,1)] = 0 + output[ligne, colonne] = np.logical_and(current_val > seuil, + current_val > np.amax(voisinage)) + print(output) + return(output) + +def coord_maxlocal(image,seuil,size_patch=3): + #seuil=np.mean(image_extrema) + """ Array -> Array """ + x,y = np.nonzero(maxlocal(image,seuil)) + return np.array((y,x)).T + +def get_corners(pict): + image_harris = harris(pict,sigma=3,kappa=0.06) + corners = coord_maxlocal(image_harris,np.mean(image_harris)) + return corners + +pict1 = np.array(Image.open("set1-1.png").convert('L'),dtype='float') +pict2 = np.array(Image.open("set1-2.png").convert('L'),dtype='float') +corners1=get_corners(pict1) +corners2=get_corners(pict2) diff --git a/453/Cours/hough.py b/453/Cours/hough.py new file mode 100644 index 0000000..85a23f8 --- /dev/null +++ b/453/Cours/hough.py @@ -0,0 +1,40 @@ + def calcul_acc_cercles(img_s, rad_min=5, rad_max=100): + #init acc : + [c_max, r_max] = img_s.shape + r_min=1 + delta_r = 1 + N_r = int((r_max-r_min+delta_r)/delta_r) + delta_c = 1 + c_min = 1 + N_c = int((c_max-c_min+delta_c)/delta_c) + delta_rad = 1 + N_rad = int((rad_max-rad_min+delta_rad)/delta_rad) + acc = np.zeros((N_rad,N_r,N_c)) + print("Taille de l'accumulateur" + str(acc.shape)) + x,y = np.nonzero(img_s) + for i in range(len(x)): + for r in range(r_min, r_max+1, delta_r): + for c in range(c_min, c_max+1, delta_c): + if (x[i],y[i]) != (r,c): + rad = np.sqrt((r-x[i])**2 + (c-y[i])**2) + if rad < rad_max and rad > rad_min: + i_id = int((r-r_min)/delta_r) + j_id = int((c-c_min)/delta_c) + k_id = int((rad-rad_min)/delta_rad) + acc[k_id,i_id,j_id] += 1 /rad + return acc +def cherche_N_maxima_cercles(accumulateur, exclusion_xy, +exclusion_rayon, N): + accu2 = np.copy(accumulateur) + [rad_max0, c_max0, r_max0] = accumulateur.shape + liste_max = [] + for cercle in range(N): + [rayon, w_0, h_0] = np.unravel_index(np.argmax(accu2),accu2.shape) + rayon += 5 + w_0 += 1 + h_0 += 1 + accu2[rayon - exclusion_rayon : rayon + exclusion_rayon, + w_0 -exclusion_xy : w_0 + exclusion_xy, + h_0 - exclusion_xy : h_0 + exclusion_xy] =0 + liste_max.append([rayon, w_0, h_0]) + return liste_max diff --git a/453/Cours/main.tex b/453/Cours/main.tex new file mode 100644 index 0000000..65fc259 --- /dev/null +++ b/453/Cours/main.tex @@ -0,0 +1,29 @@ +\documentclass[openany]{../../cours} +\usepackage{../../raccourcis} +% bug avec minted sinon +\let\framed\relax \let\endframed\relax + \let\shaded\relax \let\endshaded\relax + \let\leftbar\relax \let\endleftbar\relax + \let\snugshade\relax \let\endsnugshade\relax + \usepackage{minted} + \setminted{linenos=true, tabsize=4, fontsize=\small, xleftmargin=5pt, xrightmargin=5pt,bgcolor=gray!10,frame=lines,framesep=2mm} +% Mise en page +\title{Notes de Cours} +\author{Pierre-Antoine Comby} +\teacher{Emmanuel Aldea \& Thomas Rodet} +\module{453 - Traitement d'Image} +\usepackage{multicol} + +\begin{document} + +\maketitle +\chapter{Introduction au traitement d'image} +\subfile{chap1.tex} +\chapter{Chapitre2} + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: t +%%% End: diff --git a/raccourcis.sty b/raccourcis.sty index 5797307..0b9cf0c 100755 --- a/raccourcis.sty +++ b/raccourcis.sty @@ -38,7 +38,8 @@ \newcommand{\ddd}[2]{\frac{\text{d}^2 #1}{\text{d} {#2}^2}} \newcommand{\dr}[2]{\frac{\partial #1}{\partial #2}} \newcommand{\divv}{\text{div}} -% Macros mieux :) +\newcommand{\grad}{\text{grad}} +%Macros mieux :) \newcommand{\chimie}[1]{$\mathrm{#1}$} \newcommand{\chimiecite}[1]{\[\mathrm{#1}\]} \newcommand{\nchim}{\mathit{n}}