final update of the report
This commit is contained in:
parent
2633662c4b
commit
036cdeb76e
203
rapport.org
203
rapport.org
@ -18,6 +18,8 @@ src_latex{\newpage}
|
||||
|
||||
Le but de ce projet a été de créer un logiciel permettant la compression d’images via la détection de surfaces d’une même couleur, ainsi que de permettre la décompression du fichier ainsi généré à la compression en une image identique à l’image d’origine. Il est à noter que cet algorithme est orienté vers les images de style /comics/ ou avec peu de couleurs, d’où l’image test que vous trouverez en [[*Image de test][annexe]] qui fut mon image de test principale.
|
||||
|
||||
Le lien de l’énoncé original se situe également en annexe. Une documentation du projet sera également disponible en suivant le lien donné également en annexe.
|
||||
|
||||
* Résolution initiale du problème
|
||||
|
||||
Afin de résoudre ce problème, je me suis basé sur un algorithme de M. Jean-Jaques Bourdin et l’ai adapté au problème de la manière suivante : pour chaque pixel de l’image, je teste si le pixel courant a une couleur identique à la couleur d’une surface, ou zone, déjà existante. Si une telle zone n’existe pas déjà, alors je la créé puis pour chaque pixel de la ligne courante je me déplace à l’extrême gauche et à l’extrême droite du segment de la même couleur que le pixel courant. Ainsi on obtient un segment de couleur unie, et il est donc possible de maintenant stocker uniquement les limites de ce segment et à l’ajouter à la collection de segments de la zone, qui elle contient les informations de la couleur de la zone. Ensuite, pour chaque pixel en dessus puis en dessous de ce segment, on répète la même procédure. Cela permet de lister toutes les zones contigües et de les ajouter ainsi à la zone courante.
|
||||
@ -60,7 +62,8 @@ Avec dix exécutions successives, sur le même processeur avec les mêmes argume
|
||||
|
||||
** Sortir du concept d’image avec des lignes et colonnes
|
||||
*** Théorie
|
||||
L’algorithme actuel considère le fichier d’entrée comme étant une image en deux dimensions, limitant ainsi les segments au début d’une ligne de pixels même si le pixel précédent dans le vecteur dans lequel ces derniers sont stockés est de la même couleur, divisant ainsi des segments en plusieurs segments inutilement. En ignorant ces fins de lignes pour ne procéder qu’à l’analyse des pixels comme ne faisant partie que d’une ligne unique permettrait d’éviter ces ajouts inutiles de segments, et ainsi économiser un peu d’espace avec le fichier compressé. Cependant, je ne pense pas que cette solution soit aussi efficace que la possibilité suivante avec laquelle la possibilité actuelle d’optimisation n’est pas compatible.
|
||||
L’algorithme actuel considère le fichier d’entrée comme étant une image en deux dimensions, limitant ainsi les segments au début d’une ligne de pixels même si le pixel précédent dans le vecteur dans lequel ces derniers sont stockés est de la même couleur, divisant ainsi des segments en plusieurs segments inutilement. En ignorant ces fins de lignes pour ne procéder qu’à l’analyse des pixels comme ne faisant partie que d’une ligne unique permettrait d’éviter ces ajouts inutiles de segments, et ainsi économiser un peu d’espace avec le fichier compressé. Cependant, je ne pense pas que cette solution soit réellement efficace du fait du peu de cas où ce problème se pose.
|
||||
|
||||
*** Modifications apportées
|
||||
Les deux boucles de la fonction ~addPixelToSelectedZone~ cherchant les segments ont été modifiées ainsi :
|
||||
#+BEGIN_SRC C
|
||||
@ -85,29 +88,205 @@ Cette modification permet d’ignorer les retours à la ligne et ainsi potentiel
|
||||
*** Résultats
|
||||
Hélas le gain d’espace n’est pas flagrant, et cela s’explique par le faible nombre de lignes de pixels de l’image, 1500 dans ce cas, et du fait qu’économiser deux ~uint32_t~ par ligne est une amélioration mineure, permettant dans le cas de l’image ~asterix.ppm~ d’économiser uniquement 12 kilo octets. La différence sur le fichier compressé de 2.3 méga octets d’origine est donc négligeable. Je ne continuerai donc pas de travailler sur cette piste.
|
||||
|
||||
** Espace de stockage
|
||||
*** Théorie
|
||||
Actuellement, chaque position de pixel est stockée via deux ~uint64_t~ donnant l’extrême gauche et l’extrême droite d’un segment, ainsi stockant le segment individuel sur seize octets. L’emplacement est considéré comme étant l’emplacement sur un vecteur à dimension unique stockant successivement tous les pixels. Il est possible à la place de cette méthode de considérer la dimension duelle de l’image et de ne stocker que la position verticale du segment et ses extrêmes gauche et droit sur un ~uint32_t~. Cela permet donc d’indexer des segments sur des images de 2^{64} pixels (plus de quatre milliards de pixels) de haut et de large, ce qui est largement suffisant pour la majorité des cas, tout en utilisant quatre octets de moins que pour la version actuelle du programme. Il serait également possible de stocker ces valeurs sur trois ~uint16_t~, n’utilisant ainsi que six octets au total et limitant la taille maximale de l’image d’entrée à 2^{16} pixels de haut et de large.
|
||||
*** Modifications apportées
|
||||
*** Résultats
|
||||
|
||||
** Passer à de la compression à pertes
|
||||
*** Théorie
|
||||
Il existe toujours la possibilité de ne plus faire que de la compression sans pertes mais de faire également de la compression avec pertes, en acceptant en argument un taux de tolérance quant à la similarité des couleurs entre elles. Ainsi, cela éliminera certaines couleurs de l’image et économisera ainsi des zones disposant d’un petit nombre de zones ou de segments en les assimilant aux couleurs qui leur sont proches. L’inconvénient est qu’avec ce paramètre, il sera impossible de restituer l’image d’origine à l’identique.
|
||||
|
||||
*** Modifications apportées
|
||||
Seuls trois fichiers ont eu à être modifiés : ~main.c~, ~compress.h~ et ~compress.c~. Voici ci-dessous les modifications apportées à chacun de ces fichiers (sortie de la commande ~git diff~) :
|
||||
#+BEGIN_SRC text
|
||||
diff --git a/src/compress.c b/src/compress.c
|
||||
index 191265a..25e43aa 100644
|
||||
--- a/src/compress.c
|
||||
+++ b/src/compress.c
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "compress.h"
|
||||
|
||||
+int tolerance;
|
||||
+
|
||||
/**
|
||||
,* Cette fonction permet d’évaluer si le pixel passé en argument est éligible à
|
||||
,* la zone passée également en argument.
|
||||
@@ -14,10 +16,17 @@
|
||||
,* \return Valeur booléenne, `1` si le pixel est éligible, `0` sinon
|
||||
,*/
|
||||
int32_t sameColor(Pixel *t_pixel, Zone *t_zone) {
|
||||
- return (t_pixel->red == t_zone->red && t_pixel->green == t_zone->green &&
|
||||
- t_pixel->blue == t_zone->blue)
|
||||
- ? 1
|
||||
- : 0;
|
||||
+ int diff_red, diff_green, diff_blue;
|
||||
+ if(tolerance == 0) {
|
||||
+ return (t_pixel->red == t_zone->red && t_pixel->green == t_zone->green &&
|
||||
+ t_pixel->blue == t_zone->blue)
|
||||
+ ? 1
|
||||
+ : 0;
|
||||
+ }
|
||||
+ diff_red = (abs((int32_t)t_zone->red - (int32_t)t_pixel->red) * 100) / 255;
|
||||
+ diff_green = (abs((int32_t)t_zone->green - (int32_t)t_pixel->green) * 100) / 255;
|
||||
+ diff_blue = (abs((int32_t)t_zone->blue - (int32_t)t_pixel->blue) * 100) / 255;
|
||||
+ return ((diff_red + diff_green + diff_blue) / 3) <= tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,13 +182,15 @@ void write_compressed_file(Image *t_img, FILE *t_output, darray *t_zones) {
|
||||
,* \param[in] t_input_file Nom/chemin du fichier `.ppm` d'entrée
|
||||
,* \param[in] t_output_file Nom/chemin du fichier `.su` de sortie
|
||||
,*/
|
||||
-void compress(const char *t_input_file, const char *t_output_file) {
|
||||
+void compress(const char *t_input_file, const char *t_output_file,
|
||||
+ int32_t t_tolerance) {
|
||||
Image *img;
|
||||
darray *zones;
|
||||
FILE *output_file;
|
||||
if (!t_output_file) {
|
||||
t_output_file = DEFAULT_COMPRESSED_NAME;
|
||||
}
|
||||
+ tolerance = t_tolerance;
|
||||
img = newImage();
|
||||
imageLoadPPM(t_input_file, img);
|
||||
output_file = get_file(t_output_file, "wb");
|
||||
diff --git a/src/compress.h b/src/compress.h
|
||||
index 9b2f832..50f7c25 100644
|
||||
--- a/src/compress.h
|
||||
+++ b/src/compress.h
|
||||
@@ -23,6 +23,7 @@ void write_segments(FILE *t_output, darray *t_segments);
|
||||
/// Écrit les données compressées dans le fichier de sortie
|
||||
void write_compressed_file(Image *t_img, FILE *t_output, darray *t_zones);
|
||||
/// Compresse l'image d'entrée
|
||||
-void compress(const char *t_input_file, const char *t_output_file);
|
||||
+void compress(const char *t_input_file, const char *t_output_file,
|
||||
+ int32_t tolerance);
|
||||
|
||||
#endif /* SRC_COMPRESS_H_ */
|
||||
diff --git a/src/main.c b/src/main.c
|
||||
index 8ba5bee..4676ca7 100644
|
||||
--- a/src/main.c
|
||||
+++ b/src/main.c
|
||||
@@ -24,18 +24,21 @@
|
||||
,*/
|
||||
void help(int t_exit_code) {
|
||||
puts("Usage:\n"
|
||||
- "surfaces-unies -i path [-o path] [-options]\n\n"
|
||||
+ "surfaces-unies -i path [-o path] [--options] [-t 0-100]\n\n"
|
||||
"The default action is to compress the mandatory input image to a .su\n"
|
||||
"file saved in the current directory.\n"
|
||||
"The input image MUST be saved in the ppm format.\n"
|
||||
"Options available:\n"
|
||||
- "-h --help\n\tdisplay the current message\n"
|
||||
- "-i --input\n\tpath to the input file (MANDATORY)\n"
|
||||
+ "-h --help\n\tDisplay the current message\n"
|
||||
+ "-i --input\n\tPath to the input file (MANDATORY)\n"
|
||||
"-o --output\n"
|
||||
- "\tpath to the output file (if the file already exists, it will be\n"
|
||||
+ "\tPath to the output file (if the file already exists, it will be\n"
|
||||
"\toverwritten)\n"
|
||||
- "-c --compress\n\tcompress the input file\n"
|
||||
- "-u --uncompress\n\tuncompresses the input file to the output file.");
|
||||
+ "-t --tolerance\n"
|
||||
+ "\tColor tolerance for lossy compression. By default at 0 for lossless\n"
|
||||
+ "\tcompression, at 100 will consider every color to be the same.\n"
|
||||
+ "-c --compress\n\tCompress the input file\n"
|
||||
+ "-u --uncompress\n\tUncompresses the input file to the output file.");
|
||||
exit(t_exit_code);
|
||||
}
|
||||
|
||||
@@ -49,9 +52,10 @@ void help(int t_exit_code) {
|
||||
,* supplémentaires aux fonctions.
|
||||
,*/
|
||||
struct Argres {
|
||||
- char *input; /*!< Nom du fichier d'entrée */
|
||||
- char *output; /*!< Nom du fichier de sortie */
|
||||
- char compress; /*!< Le fichier d'entrée doit-il être compressé ? */
|
||||
+ char *input; /*!< Nom du fichier d'entrée */
|
||||
+ char *output; /*!< Nom du fichier de sortie */
|
||||
+ char compress; /*!< Le fichier d'entrée doit-il être compressé ? */
|
||||
+ int32_t tolerance; /*!< Tolérance en pourcentage des différences de couleur */
|
||||
};
|
||||
typedef struct Argres Argres;
|
||||
|
||||
@@ -72,6 +76,12 @@ void get_args(Argres *t_args, const int *const t_c) {
|
||||
case 'o': (*t_args).output = optarg; break;
|
||||
case 'c': (*t_args).compress = 1; break;
|
||||
case 'u': (*t_args).compress = 0; break;
|
||||
+ case 't':
|
||||
+ (*t_args).tolerance = atoi(optarg);
|
||||
+ if(t_args->tolerance < 0 || t_args->tolerance > 100) {
|
||||
+ help(ARGERROR);
|
||||
+ }
|
||||
+ break;
|
||||
case '?':
|
||||
default: help(ARGERROR);
|
||||
}
|
||||
@@ -95,16 +105,18 @@ Argres process_args(const int t_argc, char *t_argv[]) {
|
||||
res.input = NULL;
|
||||
res.compress = 1;
|
||||
res.output = NULL;
|
||||
+ res.tolerance = 0;
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"output", required_argument, NULL, 'o'},
|
||||
+ {"tolerance", required_argument, NULL, 't'},
|
||||
{"compress", no_argument, NULL, 'c'},
|
||||
{"uncompress", no_argument, NULL, 'u'},
|
||||
{NULL, 0, NULL, 0}};
|
||||
- int c = getopt_long(t_argc, t_argv, "hi:o:cu", long_options, &option_index);
|
||||
+ int c = getopt_long(t_argc, t_argv, "hi:o:t:cu", long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
get_args(&res, &c);
|
||||
@@ -129,8 +141,8 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr, "ERROR: no input file.");
|
||||
help(ARGERROR);
|
||||
}
|
||||
- if(argresults.compress) {
|
||||
- compress(argresults.input, argresults.output);
|
||||
+ if (argresults.compress) {
|
||||
+ compress(argresults.input, argresults.output, argresults.tolerance);
|
||||
} else {
|
||||
uncompress(argresults.input, argresults.output);
|
||||
}
|
||||
#+END_SRC
|
||||
*** Résultats
|
||||
La nouvelle option ~-t~ est une option très sensible qui peut profondément changer la qualité de l’image dès des valeurs basses, aux alentours de 15 à 25. Cependant, une nette réduction de la taille de l’image compressée peut être constatée, ainsi qu’une baisse notable du temps de compression de l’image dû aux recherches des zones correspondant aux pixels moins fréquentes, et lorsqu’elles se produisent, la recherche se fait parmi moins de zones. Ainsi, pour différentes valeurs de tolérance, on obtient ces résultats :
|
||||
| / | < | < |
|
||||
| tolérance | vitesse d’exécution | taille de l’image compressée |
|
||||
|-----------+---------------------+------------------------------|
|
||||
| 0 | 7.37s | 2.3Mo |
|
||||
| 5 | 0.85s | 2.0Mo |
|
||||
| 10 | 0.82s | 2.0Mo |
|
||||
| 15 | 0.86s | 2.0Mo |
|
||||
| 20 | 0.84s | 1.9Mo |
|
||||
| 25 | 0.85s | 1.9Mo |
|
||||
| 30 | 0.92s | 1.8Mo |
|
||||
| 40 | 1.18s | 2.1Mo |
|
||||
| 50 | 0.90s | 1.5Mo |
|
||||
| 60 | 1.41s | 1.9Mo |
|
||||
| 70 | 1.34s | 1.8Mo |
|
||||
| 80 | 1.40s | 1.9Mo |
|
||||
| 90 | 1.42s | 1.9Mo |
|
||||
| 100 | 0.17s | 12Ko |
|
||||
On remarque cependant qu’après environ 30% de tolérance de couleur, les temps ont tendance à remonter et les fichiers ont tendance à devenir plus lours, excepté pour la tolérance de 50%. Je suppose qu’il s’agit du fait d’un grand nombre de segments contigus de couleur différente s’alternant rapidement. En tous cas, bien qu’on aie en effet quelques améliorations concernant la taille de l’image compressée, je ne considère personellement pas cette technique comme en vallant la peine du fait d’artéfacts facilement visibles dès les premières valeurs proches de 0, comme on peut le voir sur l’image ci-dessous, compressée avec une tolérance de 5% :
|
||||
src_latex{\newpage}
|
||||
#+ATTR_LATEX: :width 4.0in
|
||||
[[./img/asterix5p.png]]
|
||||
|
||||
* Associations des différentes propositions
|
||||
|
||||
Il est également possible d’associer certaines des propositions précédentes améliorantes afin de tester si elles peuvent davantage améliorer le projet.
|
||||
|
||||
* Conclusion
|
||||
Dans le cas où cela ne serait pas bien visible en version papier, une version digitale de ce document est disponible en ligne, voir le lien donnée en [[*Liens][annexes]].
|
||||
|
||||
src_latex{\newpage}
|
||||
* Annexes
|
||||
** Liens
|
||||
- Énoncé :: [[http://www.ai.univ-paris8.fr/~jj/Cours/Algo/AA18.html#projets]] (projet n°22)
|
||||
- Dépôt :: [[https://labs.phundrak.fr/phundrak/surfaces-unies/]]
|
||||
- Image de test :: [[https://labs.phundrak.fr/phundrak/surfaces-unies/blob/master/img/asterix.ppm]]
|
||||
- Image compressée avec pertes :: [[https://labs.phundrak.fr/phundrak/surfaces-unies/blob/master/img/asterix5p.png]]
|
||||
- Version digitale de ce document :: [[https://phundrak.fr/surfaces-unies/rapport.pdf]]
|
||||
- Documentation du code source :: [[https://phundrak.fr/surfaces-unies/]]
|
||||
- Version PDF de la documentation du code source :: [[https://phundrak.fr/surfaces-unies/refman.pdf]]
|
||||
src_latex{\newpage}
|
||||
** Image de test
|
||||
|
||||
|
BIN
rapport.pdf
BIN
rapport.pdf
Binary file not shown.
Loading…
Reference in New Issue
Block a user