Travaux : C

Réduire le bruit numérique sur les photos ? (dans C, le 30/12/2009 à 13h 21min)

Pour commencer, un petit rappel :
Citation : Linternaute
Le bruit est un terme issu du domaine de l'acoustique et désigne un signal parasite. Que ce soit pour le son ou pour l'image, le principe est identique : sur tout signal de base vient s'adjoindre un ensemble d'informations parasites aléatoires.
Si le niveau du signal est suffisant, la proportion de bruit dans le signal utile (le fameux rapport signal/bruit) reste insignifiante. Par contre, si le niveau de bruit prend le pied sur l'information principale, le bruit sera présent.
On l'aura compris, le bruit est donc quelque chose dont on se passerai bien.

Je me suis alors demandé si le bruit numérique sur une image, qui se fait de plus en plus présent avec la montée en sensibilité ISO est une variable aléatoire ou non. Autrement dit, est-ce que dans des conditions rigoureusement identiques, deux prises de vues présentent le même bruit ou non ? La réponse est non, du moins sur mon canon 350D, et je pense ne pas trop m'avancer en disant qu'il en est de même pour tout les reflex numériques du moment. Après tout, les capteurs CMOS ne changent pas radicalement d'un appareil à l'autre, si ?

A partir de là, vu que le bruit n'est qu'une déformation des composantes de la couleur de chacun des pixels constituant l'image (un peu trop de rouge, un peu moins de vert...), je me suis dit qu'en faisant la moyenne des valeurs du même pixel sur plusieurs prise de vues, on devrait statistiquement tomber sur la valeur exacte du pixel.
Pour vérifier cette hypothèse, j'ai écrit un petit programme en C prenant en entrée plusieurs prises de vues du même sujet, en faisant la moyenne de chaque pixel. Et devinez quoi ? Ça marche !
La réduction du bruit est flagrante avec 5 images, comme on peut le voir avec les images ci-dessous :


image réduite de l'article
Une des images de base

image réduite de l'article image réduite de l'article image réduite de l'article image réduite de l'article
Et les autres, à titre d'information

image réduite de l'article
Le rendu du programme

On constate facilement que le flou dû à le profondeur de champs est nettement moins bruité, alors que l'on conserve tous les détails du premier plan, notamment le grain du plastique qui n'a donc pas été traité comme du bruit.

Il reste maintenant à savoir si il y a un réel avantage à réduire le bruit en augmentant le nombre de prise de vue, étant donné que la montée en ISO doit nous permettre de réduire le temps d'exposition. Alors si il faut ensuite multiplier ce temps par 5, on se mord la queue...

Voici le code du programme, qui se base sur la SDL pour travailler sur les images. Il n'y a rien de bien intéressant, mais si vous voulez tester, vous pouvez ;)
Code : C
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include "SDL/SDL.h"
  4. #include "SDL/SDL_image.h"
  5.  
  6. #define NB_IMG 5
  7. #define NB_IMG_USE 5
  8. static char* liste_img[NB_IMG] = {"crop1.jpg", "crop2.jpg", "crop3.jpg", "crop4.jpg", "crop5.jpg" };
  9. void pause();
  10.  
  11. Uint32 getPixel(SDL_Surface *surface, int x, int y){
  12.   int nbOctetsParPixel = surface->format->BytesPerPixel;
  13.   Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * nbOctetsParPixel;
  14.   switch(nbOctetsParPixel){
  15.   case 1:
  16.     return *p;
  17.  
  18.   case 2:
  19.     return *(Uint16 *)p;
  20.  
  21.   case 3:
  22.     if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
  23.       return p[0] << 16 | p[1] << 8 | p[2];
  24.     else
  25.       return p[0] | p[1] << 8 | p[2] << 16;
  26.  
  27.   case 4:
  28.     return *(Uint32 *)p;
  29.  
  30.   default:
  31.     return 0;
  32.   }
  33. }
  34.  
  35. void setPixel(SDL_Surface *surface, int x, int y, Uint32 pixel){
  36.   int nbOctetsParPixel = surface->format->BytesPerPixel;
  37.   Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * nbOctetsParPixel;
  38.   switch(nbOctetsParPixel){
  39.   case 1:
  40.     *p = pixel;
  41.     break;
  42.  
  43.   case 2:
  44.     *(Uint16 *)p = pixel;
  45.     break;
  46.  
  47.   case 3:
  48.     if(SDL_BYTEORDER == SDL_BIG_ENDIAN){
  49.       p[0] = (pixel >> 16) & 0xff;
  50.       p[1] = (pixel >> 8) & 0xff;
  51.       p[2] = pixel & 0xff;
  52.     }
  53.     else{
  54.       p[0] = pixel & 0xff;
  55.       p[1] = (pixel >> 8) & 0xff;
  56.       p[2] = (pixel >> 16) & 0xff;
  57.     }
  58.     break;
  59.  
  60.   case 4:
  61.     *(Uint32 *)p = pixel;
  62.     break;
  63.   }
  64. }
  65.  
  66.  
  67. int main(int argc, char *argv[]){
  68.   SDL_Surface *ecran = NULL, *image = NULL;
  69.   SDL_Rect positionFond;
  70.   Uint8 r, g, b;
  71.   Uint32 rgb;
  72.   Uint16 *rendu_array = NULL;
  73.   Uint8 *p = NULL;
  74.   int i, j, k;
  75.  
  76.   positionFond.x = 0;
  77.   positionFond.y = 0;
  78.   SDL_Init(SDL_INIT_VIDEO);
  79.   SDL_WM_SetCaption("Reduction du bruit", NULL);
  80.   image = IMG_Load(liste_img[0])
  81.   ecran = SDL_SetVideoMode(image->w, image->h, 32, SDL_HWSURFACE);
  82.   rendu_array = malloc(image->w * image->h * 3 * sizeof(Uint16));
  83.  
  84.   for(k = 0; k < NB_IMG_USE; k++){
  85.     image = IMG_Load(liste_img[k]);
  86.     for(i = 0; i < image->h; i++){
  87.       for(j = 0; j < image->w; j++){
  88.    rgb = getPixel(image, j, i);
  89.    rendu_array[(i*image->w+j)*3] += (rgb >> 16) & 0x000000FF;
  90.    rendu_array[(i*image->w+j)*3+1] += (rgb >> 8) & 0x000000FF;
  91.    rendu_array[(i*image->w+j)*3+2] += (rgb >> 0) & 0x000000FF;
  92.       }
  93.     }
  94.   }
  95.  
  96.   for(i = 0; i < image->h; i++){
  97.     for(j = 0; j < image->w; j++){
  98.       rgb = ((((Uint32)rendu_array[(i*image->w+j)*3+2]/NB_IMG_USE) << 0) & 0x000000FF)
  99.    | ((((Uint32)rendu_array[(i*image->w+j)*3+1]/NB_IMG_USE) << 8) & 0x0000FF00)
  100.    | ((((Uint32)rendu_array[(i*image->w+j)*3]/NB_IMG_USE) << 16) & 0x00FF0000);
  101.  
  102.       setPixel(image, j, i, rgb);
  103.     }
  104.   }
  105.  
  106.  
  107.   SDL_SaveBMP(image, "render.bmp");
  108.   SDL_BlitSurface(image, NULL, ecran, &positionFond);
  109.  
  110.   SDL_Flip(ecran);
  111.   pause();
  112.   SDL_FreeSurface(image);
  113.   SDL_Quit();
  114.   return EXIT_SUCCESS;
  115. }
  116. void pause(){
  117.   int continuer = 1;
  118.   SDL_Event event;
  119.   while (continuer){
  120.       SDL_WaitEvent(&event);
  121.       switch(event.type){
  122.    case SDL_QUIT:
  123.      continuer = 0;
  124.         }
  125.     }
  126. }
  127.  


[5 commentaire(s)]