Méthode de visualisation d'images en relief

et programme de visualisation
en C++ et Qt4

Vous avez un écran large (16x9) et vous voudriez bien regarder des photos en relief bien nettes avec tous leurs détails et vrais couleurs? Voici un moyen d'y parvenir à peu de frais. Il vous faut récupérer un prisme à 90º dans une ancienne paire de jumelles 7x35. ( on en trouve d'occasion pour une dizaine d'euro, ou moins, il y a 4 prismes par instrument). Attention: les jumelles récentes et légères, dont les tubes sont droits, comportent des prismes à 30º et 60º qui ne conviennent pas.

1 Principe

Dans les jumelles à prismes, le grossissement de l'image est obtenu par l'utilisation de deux lentilles convergentes, l'objectif (le grand côté) et l'oculaire (le petit bout de la lorgnette).

Ces lentilles sont composées de plusieurs lentilles accolées afin de former un ensemble achromatique (qui dévie toutes les couleurs de la même façon).

Cela procure une image à grand champ mais qui a l'inconvénient d'être inversée (haut-bas ET droite-gauche c'est à dire tournée comme si vous tournez une photo posée sur la table en la faisant glisser).

L'interposition de deux prismes en série entre l'objectif et l'oculaire permet de redresser l'image, en deux temps, le premier la retourne verticalement, le second horizontalement, comme sur la figure ci-contre.

2 -

Les prismes utilisés de cette manière ne décomposent pas la lumière en faisceaux de toutes les couleurs parce que les rayons franchissent la séparation air/verre perpendiculairement aux surfaces, et que de plus l'entrée d'un rayon est symétrique à sa sortie du prisme comme indiqué sur l'image ci-contre.

Une autre façon d'utiliser un prisme est illustrée sur l'image suivante.

3 -

Cette fois l'angle du rayon incident avec la surface air/verre étant quelconque, il y a décomposition spectrale suivant les lois de la réfraction, l'indice de réfraction dépendant de la nature du milieu ET de la longueur d'onde (si on nomme i1 l'angle d'incidence et i2 l'angle de réfraction, on a:

 sin( i2) = n sin ( i1) , n étant l'indice de réfraction. Voir cours de physique de 1ère.

La lumière blanche, composée de toutes les couleurs, se trouve donc décomposée (on dit "dispersée") par le prisme, on obtient un spectre.

C'est très joli, ça débouche sur plein d'applications vachement utiles comme la spectroscopie (bien que pour cela on utilise plutôt des réseaux), mais c'est très gênant si on veut utiliser un prisme pour dévier légèrement un rayon lumineux sans l'altérer.



4 -

Mais il existe une autre manière d'utiliser un prisme: si on annule l'angle d'incidence (lorsque le rayon entre perpendiculaire à la surface d'un petit coté), il n'y a plus de réfraction, le rayon file tout droit à l'intérieur du prisme, il se réfléchit sur le grand coté (l'hypoténuse du triangle rectangle), et ressort perpendiculaire à la surface de sortie (dioptre), donc sans être décomposé. L'angle de déviation obtenu avec des prismes rectangle de jumelles est de 90º. voir figure ci-contre.

5 -

Mais il s'avère, comme le montre la figure ci-contre, que si on change cet angle d'incidence en gardant le principe de la réflexion sur l'hypoténuse, IL N'Y A TOUJOURS PAS DE DÉCOMPOSITION SPECTRALE. Cela vient du fait que la décomposition interne de la lumière est exactement compensés par une recomposition de la lumière blanche à la sortie du prisme, par raison de symétrie. (Oups, oui je sais, mon prof de math me l'avait expliqué, la raison de symétrie est une notion que la raison ignore, mais je suis incorrigible !) Je vous laisse faire plein de calculs d'optique géométrique. En clair, les différentes couleurs ressortent parallèles, et l'œil voit une image impeccable pas du tout irisée, juste DÉVIÉE et INVERSEE.

6 Application à la stéréoscopie

J'ai découvert cela par hasard et j'ai trouvé que c'était très utile pour obtenir une vue en relief à partir de deux images stéréoscopiques affichées côte à côte à l'écran. ( L'image de droite devant être préalablement inversée droite-gauche). Voir figure ci-contre.

Vous devez viser du regard l'image de gauche, tout en tenant le prisme de la main droite entre le pouce et l'index (par les faces non-optiques...), le disposer devant l'œil droit, en l'orientant précisément de façon à ce que cet œil aperçoit l'image de droite superposée à celle de gauche.

Le relief obtenu est saisissant, les couleurs sont intactes, contrairement à ce qui ce produit avec les anaglyphes.

Le seul inconvénient est que la taille horizontale de l'image ne peut excéder la moitié de celle de l'écran.

Lorsque le prisme est bien disposé, vous devez voir non pas 3 ou 4 images mais deux comme à l'œil nu. SI VOTRE DOUBLE-IMAGE EST BIEN CENTREE HORIZONTALEMENT A L'ECRAN, vous ne devez voir qu'un seul écran d'ordinateur et pas 1,5 ou 2 etc...

Le prisme doit être tenu TRES PRES de l'œil droit (environ 1cm) pour obtenir un champ suffisamment grand.

7 MISE EN GARDE

Attention:

Un prisme est taillé dans un bloc de verre, et peut présenter de arêtes coupantes. Donc attention aux yeux! Je vous conseille d'émousser les arêtes tranchantes avec du papier de verre avant toute manipulation. Je décline toute responsabilité en cas de maladresse de votre part.

Pour une utilisation courante, vous devriez coller le prisme entre deux petites planchettes de façon à rendre impossible tout contact entre le prisme et l'œil.


8 La prise de vue avec deux APN:

Il est tout à fait possible de prendre un couple d'images stéréoscopiques avec un seul appareil photo, en se dépaçant d'une dizaine de centimètres entre les deux prises de vues, mais il est alors impossible d'obtenir un résultat correct avec un sujet qui bouge, comme un personnage en action, un animal ou une chute d'eau...

Avec deux APN identiques fixés ensemble sur un même support, tout cela devient possible:

9 -

10 Le PROGRAMME DE VISUALISATION en C++ et Qt4

boite code source:
CODE SOURCE en C++ Qt4
  1. #include "mainwindowimpl.h"
  2. #include <QFile>
  3. #include <QPainter>
  4. #include <QFileDialog>
  5. #include <QKeyEvent>
  6.  
  7. QString version_i = "v1.0";
  8. QString repertoire_images;
  9. int current_num_image;
  10. int w_ecran, h_ecran;
  11. int largeur_image, hauteur_image;
  12.  
  13.  
  14. MainWindowImpl::MainWindowImpl( QWidget * parent, Qt::WFlags f)
  15. : QMainWindow(parent, f)
  16. {
  17. setupUi(this);
  18. this->setWindowState(Qt::WindowMaximized);
  19. setWindowTitle("visuRelief: " + version_i);
  20. largeur_image = 640;
  21. hauteur_image = 480;
  22. imageLabel1->setGeometry(0,0,largeur_image,hauteur_image);
  23. imageLabel2->setGeometry(largeur_image+1,0,largeur_image,hauteur_image);
  24. repertoire_images = "/media/";
  25. current_num_image = 1;
  26. }
  27.  
  28.  
  29. void MainWindowImpl::choix_repertoire_images(void)
  30. {
  31. QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
  32.  
  33. options |= QFileDialog::DontUseNativeDialog;
  34. QString directory = QFileDialog::getExistingDirectory(this,
  35. tr("Choix repertoire images"), repertoire_images, options);
  36.  
  37. if (!directory.isEmpty())
  38. {
  39. repertoire_images = directory + "/";
  40. current_num_image = 0;
  41. on_bouton2_clicked();
  42.  
  43. }
  44. }
  45.  
  46.  
  47. void MainWindowImpl::supprimer_images(int n)
  48. {
  49. QString fileName1;
  50. QString fileName2;
  51. QString str_n;
  52.  
  53. str_n.setNum(n); // conversion num -> txt
  54.  
  55. fileName1 = "00"+str_n;
  56. fileName1 = repertoire_images + fileName1.right(3);
  57. fileName1 += "-a.jpg";
  58.  
  59. fileName2 = "00"+str_n;
  60. fileName2 = repertoire_images + fileName2.right(3);
  61. fileName2 += "-b.jpg";
  62.  
  63. QFile file1(fileName1);
  64. if (file1.exists() ) { file1.remove(); }
  65.  
  66. QFile file2(fileName2);
  67. if (file2.exists() ) { file2.remove(); }
  68.  
  69. }
  70.  
  71. int MainWindowImpl::charger_images(int n)
  72. {
  73. QString fileName1;
  74. QString fileName2;
  75. QString str_n;
  76. int valeur = 0;
  77.  
  78. str_n.setNum(n); // conversion num -> txt
  79.  
  80. fileName1 = "00"+str_n;
  81. fileName1 = repertoire_images + fileName1.right(3);
  82. fileName1 += "-a.jpg";
  83.  
  84. fileName2 = "00"+str_n;
  85. fileName2 = repertoire_images + fileName2.right(3);
  86. fileName2 += "-b.jpg";
  87.  
  88. statusBar1->showMessage(fileName1 + " - " + fileName2);
  89.  
  90.  
  91. QFile file1(fileName1);
  92. if (file1.exists() )
  93. {
  94. QImage image1(fileName1);
  95. image1 = image1.scaledToWidth(largeur_image);
  96. if (case_miroirA->isChecked()) { image1 = image1.mirrored(true, false);}
  97. imageLabel1->setPixmap(QPixmap::fromImage(image1));
  98. }
  99. else
  100. {
  101. QPixmap pixmap1(largeur_image,hauteur_image);
  102. pixmap1.fill(Qt::darkGray);
  103. imageLabel1->setPixmap(pixmap1);
  104. valeur = 1;
  105. }
  106.  
  107. QFile file2(fileName2);
  108. if (file2.exists() )
  109. {
  110. QImage image2(fileName2);
  111. image2 = image2.scaledToWidth(largeur_image);
  112. if (case_miroirB->isChecked()) { image2 = image2.mirrored(true, false);}
  113. imageLabel2->setPixmap(QPixmap::fromImage(image2));
  114. }
  115. else
  116. {
  117. QPixmap pixmap1(largeur_image,hauteur_image);
  118. pixmap1.fill(Qt::darkGray);
  119. imageLabel2->setPixmap(pixmap1);
  120. valeur = 2;
  121. }
  122. return valeur;
  123. }
  124.  
  125.  
  126. void MainWindowImpl::keyPressEvent(QKeyEvent *event)
  127. {
  128. if ( event->key() == Qt::Key_A) { on_bouton1_clicked(); }
  129. else if ( event->key() == Qt::Key_Z) { on_bouton2_clicked(); }
  130. else if ( event->key() == Qt::Key_X) { supprimer_images(current_num_image); }
  131. }
  132.  
  133.  
  134. void MainWindowImpl::on_bouton1_clicked()
  135. {
  136. QString tx1;
  137. int valeur;
  138. if (current_num_image == 0) return;
  139.  
  140. do
  141. {
  142. current_num_image --;
  143. tx1.setNum(current_num_image); // conversion num -> txt
  144. tx1 = "00"+tx1;
  145. tx1 = tx1.right(3);
  146. Edit1->setText(tx1);
  147. valeur = charger_images(current_num_image);
  148. }
  149. while ((valeur != 0) && (current_num_image > 0));
  150.  
  151. if (current_num_image < 0) {current_num_image = 0; }
  152.  
  153. }
  154.  
  155.  
  156.  
  157. void MainWindowImpl::on_bouton2_clicked()
  158. {
  159. QString tx1;
  160. int valeur;
  161. if (current_num_image >= 999)
  162. {
  163. current_num_image = 999;
  164. return;
  165. }
  166.  
  167. do
  168. {
  169. current_num_image ++;
  170. tx1.setNum(current_num_image); // conversion num -> txt
  171. tx1 = "00"+tx1;
  172. tx1 = tx1.right(3);
  173. Edit1->setText(tx1);
  174. valeur = charger_images(current_num_image);
  175. }
  176. while ((valeur != 0) && (current_num_image < 999) );
  177.  
  178. if (current_num_image > 999) {current_num_image = 999; }
  179.  
  180. }
  181.  
  182.  
  183. void MainWindowImpl::on_case_permut_AB_toggled(bool checked)
  184. {
  185. if (checked)
  186. {
  187. imageLabel1->setGeometry(largeur_image+1,0,largeur_image,hauteur_image);
  188. imageLabel2->setGeometry(0,0,largeur_image,hauteur_image);
  189. }
  190. else
  191. {
  192. imageLabel1->setGeometry(0,0,largeur_image,hauteur_image);
  193. imageLabel2->setGeometry(largeur_image+1,0,largeur_image,hauteur_image);
  194. }
  195. }
  196.  
  197.  
  198. void MainWindowImpl::on_case_miroirA_toggled(bool)
  199. {
  200. charger_images(current_num_image);
  201. }
  202.  
  203.  
  204. void MainWindowImpl::on_case_miroirB_toggled(bool)
  205. {
  206. charger_images(current_num_image);
  207. }
  208.  
  209.  
  210. void MainWindowImpl::on_actionChoix_du_repertoire_images_activated()
  211. {
  212. choix_repertoire_images();
  213. }
  214.  
  215.  
  216.  
  217. void MainWindowImpl::on_bouton0_clicked()
  218. {
  219. QString tx1;
  220. current_num_image = 0;
  221. tx1.setNum(current_num_image); // conversion num -> txt
  222. tx1 = "00"+tx1;
  223. tx1 = tx1.right(3);
  224. Edit1->setText(tx1);
  225. charger_images(current_num_image);
  226. }
  227.  
  228.  
  229. void MainWindowImpl::on_bouton4_clicked()
  230. {
  231. QString tx1;
  232. current_num_image = 999;
  233. tx1.setNum(current_num_image); // conversion num -> txt
  234. tx1 = "00"+tx1;
  235. tx1 = tx1.right(3);
  236. Edit1->setText(tx1);
  237. charger_images(current_num_image);
  238. }
  239.  
  240.  
  241.  
  242.  
  243.  
  244. void MainWindowImpl::resizeEvent(QResizeEvent *event)
  245. {
  246. w_ecran = event->size().width();
  247. h_ecran = event->size().height();
  248. event->accept();
  249. largeur_image = w_ecran/2;
  250. hauteur_image = (largeur_image * 3) / 4;
  251. if (case_permut_AB->isChecked())
  252. {
  253. imageLabel1->setGeometry(largeur_image+1,0,largeur_image,hauteur_image);
  254. imageLabel2->setGeometry(0,0,largeur_image,hauteur_image);
  255. }
  256. else
  257. {
  258. imageLabel1->setGeometry(0,0,largeur_image,hauteur_image);
  259. imageLabel2->setGeometry(largeur_image+1,0,largeur_image,hauteur_image);
  260. }
  261. charger_images(current_num_image);
  262. }
  263.  

11 -

J'ai écrit ce programme qui permet de positionner automatiquement à l'écran les deux images constituant le couple stéréoscopique, de diverses manières. On peut ainsi les regarder:
  • avec la méthode du prisme que je viens de décrire
  • mais aussi en "louchant" (les images sont alors permutées, mais pas retournées)
Il comprend également des fonctions de sélection de répertoire, de navigation dans les images à la souris ou au clavier, et de suppression du couple d'images actuellement projeté.
Comme à mon habitude, je fournis le code source et l'exécutable pour Linux, en open source libre de droits.

12 Sources du programme de visualisation

13 Des images en relief:

14 Liens externes:

15 -



16818