Table Of Contents

Previous topic

Welcome to Pyscan3D’s documentation!

Next topic

English

This Page

Français

En cours

Résumé

Pyscan3D est un projet visant à concevoir un scanner laser 3D Open Source à faible coût.

Il existe de nombreux projet à travers le Net, mais aucun de ces projets n’est vraiment abouti. De fait, aucun de ces projets n’arrivent vraiment à percer et à concurencer les projets propriétaires. Le projet Pyscan3D vise à corriger ce défaut.

Placé sous licence GPL, vous serez libre de vous en servir aussi bien à titre personnel que professionnel, et ce gratutitement.

Codé en Python, le code est aisément accessible, et modifiable. De plus, cela permet d’envisager un portage aisé vers d’autre OS (actuellement Linux Debian uniquement)

Entièrement paramétrable, vous êtes libre de sélectionner la taille des objets à scanner.

Programmé selon certaines règles, le logiciel est basé sur une base de donnée SQLite3. Il est aisément portable dans n’importe quelle langue.

Actuellement toujours en développement, ce projet reçoit d’ore et déjà le soutien de professionnels intéressés par les possibilitées offertes.

Warning

Attention, ce projet utilise des lasers de classe 2 dangereux pour les yeux. Je ne pourrais en aucun cas être tenu pour responsable de toutes mauvaises manipulations ou autres, quelles qu’elles soient. Il appartient aux manipulateurs de prendre toutes les mesures nécessaires à leurs sécurité.

Pourquoi ce projet

Fervent defenseur du monde Libre & Open Source, je ne travaille que sous Linux depuis plusieurs années. Quand je me suis intéressé à l’univers de la 3D, lors de l’acquisition de ma CNC, j’ai cherché à acquérir ou construire un scanner 3D Libre.

Malheureusement, ma deception fut grande. Aucun projet réel n’existait. Ou je trouvais un morceau de code, ou un morceau de hardware, mais rien de concret et d’exploitable facilement et directement.

Fin 2013, en discutant avec un ami (Yoahnn, tu te reconnaitras), l’idée de développer enfin mon propre projet était lancé. Le projet lui, commença réellement en janvier 2014.

Fonctionnalités

Les principales fonctionnalités offertes par le projet sont les suivantes:

  • Choix de la taille maximale de scan au niveau hardware (voir principe de fonctionnmeent ci après)
  • Précision de scan dépendante de la webcam/camera utilisée (resolution native utilisée par le logiciel), pas de limite
  • 100% paramétrable (distances, hauteur du materiel, angle des lasers, correction de taille, ...)
  • Possibilité de sélectionner une zone precise de l’objet à scanner
  • Reglage aisé de la sensibilité afin de s’adapter aux différentes matières et couleurs d’objets
  • Actuellement, deux formats d’enregistrement disponible: OBJ et XYZ (en nuage de points)
  • Dispositifs (traitement d’image) afin d’optimiser la qualité de la numerisation

En cours d’études (implémentation non encore certaine):

  • Export en STL
  • Orientation de la webcam paramétrable (mode portrait et paysage)
  • Possibilité de scan sans chambre noire (AbsDiff OpenCV sur l’objet entre laser eteint et allumé)
  • Reduction du volume du scanner (Pour les plans hardwares qui seront fournis sur le site, reduction visée: 20%)
  • Optimisation du calcul de coordonnées 3D (notamment en z)

Presentation rapide

_images/CIMG1323.JPG _images/CIMG1328.JPG _images/CIMG1329.JPG _images/CIMG1331.JPG

Principe

Images issues de wikipedia et de developpez.com

Le principe de base est relativement simple. L’objet est placé sur un plateau rotatif. Une ligne laser est projetée sur l’objet et dessine alors son contour. Une camera, déalé d’un certains angle, filme cette ligne. Après, il suffit de faire faire un tour complet à l’objet.

_images/3dscanner.jpg

Tout repose sur la décomposition de l’image en pixels. En connaissant la vision angulaire de la webcam en vertical et en horizontal, nous pouvons déterminer les valeurs angulaires d’un pixel.

_images/Matrice_webcam.jpg

Ensuite, il ne s’agit ni plus ni moins que de trigonometrie.

_images/psa502.jpg _images/Scanner3D.jpg

En fixant nous même certains paramètres, tels l’angle des lasers (60° ici) ou encore de la distance entre la camera et le centre du plateau rotatif, nous pouvons déterminer une partie des coordonnées (X & Y, correspondant à h et L1).

Pour déterminer z, nous avons besoin de connaitre la hauteur précise du capteur de la caméra. Puis encore par trigonométrie, nous déterminons la hauteur précise de chaque point.

En sortie, nous obtenons donc les coordonnées 3D de chaque pixels, en tenant compte bien sur de la rotation du plateau.

Logiciel

_images/Capture-PYSCAN3D.png _images/Capture-PYSCAN3D-1.png

Le logiciel est réellement le plus important dans ce projet.

En effet, comme il est entierement paramétrable, la seule limite concernant la taille de l’objet à scanner, est la taille du scanner lui même. Si vous le désirez, vous pouvez ainsi transformer un garage en scanner géant.

La barre d’icones de menu est en haut, avec de gauche à droite:

  • Ecran de scan
  • Ecran de parametrage
  • Acces à la notice PDF
  • Fenetre “A propos de”
  • Bouton pour quitter le logiciel

La capture de gauche est l’écran de scan et celui de roite l’écran de paramétrage.

L’écran de scan est divisé en plusieurs partie. Tou d’abord les voyants de statuts, permettant de savoir l’état de divers éléments. En dessous, le nom du fichier de sortie. L’extension est au choix dans une liste déroulante. Encore en dessous, les boutons permettant de se connecter/déconnecter du scanner, Le bouton de calibration, et enfin le bouton pour lancer le scan 3D.

Sur la partie droite, nous avons une visu de la camera (lors de la calibration et du scan), plus des curseurs servant au calibrage. En dessous, la barre de progression du scan.

Les éléments de cet écran s’active ou se désactivent en fonction de l’action en cours, évitant ainsi toute mauvaise manipulation.

L’écran de paramétrage n’est accessible que lorsque le scanner est déconnecté du logiciel. Il permet de paramétrer correctement l’ensemble des informations nécessaires au bon fonctionnement du scanner. Il est à noter que le choix du mode de fonctionnement du moteur pas à pas est transmis à l’arduino lors de l’établissement de la communication série.

Des info bulles sont présentent partout dans le logiciel afin de guider l’utilisateur. Enfin, la notice PDF est aisément accessible en cas de besoin.

Hardware

La partie hardware repose sur un arduino UNO (alimenté par port USB) associé à un A4988, afin de piloter le moteur pas à pas. J’ai retenu un modele fonctionnant sous 12V, de 200 pas (1pas = 1.8°) pour ce dernier.

Les lasers et la lumière interne au scanner sont pilotés par des transistors MOSFET. L’ensemble des éléments (transistors, & A4988) sont montés sur une carte de prototypage pour arduino UNO.

Ci-dessous un schéma light du fonctionnement elec.

_images/Principe_elec.png

Ce schéma représente l’essentiel de l’électronique du scanner. Une des photos de la présentation rapide, vous permet de voir l’intérieur du scanner. Vous pourrez alors remarquer qu’il y a en plus un HUB USB secteur. Ce hub permet de n’avoir qu’un seul fil à brancher à l’ordinateur. Vous pourrez également remarquer qu’il y a non pas une mais deux alimentations. La plus grosse délivre du 12V et la plus petite du 5V.

Firmware

Voici le firmware actuel de l’arduino:

/*********************************************************************/
/*********************************************************************/
/*                    Firmware Pyscan3D V2.0                         */
/*********************************************************************/
/*  Author: Galode Alexandre    **           Date: 20140523          */
/*********************************************************************/
/*********************************************************************/

/*********  Input/Output Alias  **********/
const int a_i_pow12 = A0;
const int a_i_pow05 = A1;

boolean state_laser1 = false;
boolean state_laser2 = false;
boolean state_light = false;
boolean state_door = true;
boolean calib_mpap = false;

String reponse="123456789";

const int right_laser = 8;
const int left_laser = 9;
const int led_light = 10;
const int door = 11;

const int MPAP_EN=12;
const int MPAP_MS1=13;
const int MPAP_MS2=2;
const int MPAP_MS3=3;
const int MPAP_RST=4;
const int MPAP_SLEEP=5;
const int MPAP_STEP=6;
const int MPAP_DIR=7;



/*********  Configuration  **********/
void setup()
{
    //Serial communication
    Serial.begin(115200);

    
    // Peripherals
    pinMode(door, INPUT);  //logical
    pinMode(led_light, OUTPUT);  //logical
    pinMode(right_laser, OUTPUT);  //logical
    pinMode(left_laser, OUTPUT);  //logical
    
    //Stepper motor
    pinMode(MPAP_EN,OUTPUT);
    pinMode(MPAP_MS1,OUTPUT);
    pinMode(MPAP_MS2,OUTPUT);
    pinMode(MPAP_MS3,OUTPUT);
    pinMode(MPAP_RST,OUTPUT);
    pinMode(MPAP_SLEEP,OUTPUT);
    pinMode(MPAP_STEP,OUTPUT);
    pinMode(MPAP_DIR,OUTPUT);
    
    digitalWrite(MPAP_RST, HIGH);
    digitalWrite(MPAP_SLEEP, HIGH);
    digitalWrite(MPAP_EN, LOW);
    digitalWrite(MPAP_STEP, LOW);
    
    step_motor_config(0);
}



/*********  Main loop  **********/
void loop()
{
  char caractere;
  String emet;
  int i;
  float pow12;
  float pow05;
  float tmp;
  char tmp2;

  //si porte ouverte, state_door = false, et lumiere allumee, et arret moteur pap, et extinction laser
  if (digitalRead(door) == HIGH)
  {
    state_door = false;
    digitalWrite(led_light, HIGH);
    digitalWrite(left_laser, LOW);
    digitalWrite(right_laser, LOW);    
  }
  else
  {
    state_door = true;
    if (state_light == true)
      digitalWrite(led_light, HIGH);
    else
      digitalWrite(led_light, LOW);
      
    if (state_laser1 == true)
      digitalWrite(left_laser, HIGH);
    else
      digitalWrite(left_laser, LOW);
      
    if (state_laser2 == true)  
      digitalWrite(right_laser, HIGH);
    else
      digitalWrite(right_laser, LOW);
  }
  
  
  //Serial.print("\"" + reponse + "\"" + "\n\r");
  if (Serial.available())
  {
    caractere = Serial.read();
    tmp2 = 'b';
    
    reponse = roll_string(reponse, caractere);

  }
  
  
  if (reponse == "MPAP____+")
  {
    reponse = roll_string(reponse, '#');
    if (state_door == true)
    {
      step_motor_turn(1);
    }
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "MPAP____-")
  {
    reponse = roll_string(reponse, '#');
    if (state_door == true)
    {
      step_motor_turn(0);
    }
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "MPAP__CAL")
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    if (calib_mpap == false && state_door == true)
    {
      calib_mpap = true;
    }
    else
    {
      calib_mpap = false;
    }
  }
  
  
  if (reponse == "MPAPCONF0") //Mode 0: FULL step
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    step_motor_config(0);
  }
  
  
  if (reponse == "MPAPCONF1") //Mode 1: 1/2 step
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    step_motor_config(1);
  }
  
  
  if (reponse == "MPAPCONF2") //Mode 2: 1/4 step
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    step_motor_config(2);
  }
  
  
  if (reponse == "MPAPCONF3") //Mode 3: 1/8 step
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    step_motor_config(3);
  }
  
  
  if (reponse == "MPAPCONF4") //Mode 4: 1/16 step
  {
    reponse = roll_string(reponse, '#');
    Serial.print("ACK");
    step_motor_config(4);
  }
  
  
  if (reponse == "LIGHT__ON")
  {
    reponse = roll_string(reponse, '#');
    state_light = true;
    digitalWrite(led_light, HIGH);
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "LIGHT_OFF")
  {
    reponse = roll_string(reponse, '#');
    state_light = false;
    digitalWrite(led_light, LOW);        
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "LASER_OFF")
  {
    reponse = roll_string(reponse, '#');
    state_laser1 = false;
    state_laser2 = false;
    digitalWrite(left_laser, LOW);
    digitalWrite(right_laser, LOW);
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "LASER1_ON")
  {
    reponse = roll_string(reponse, '#');
    if (state_door == true)
    {
      state_laser1 = true;
      digitalWrite(left_laser, HIGH);
    }
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "LASER2_ON")
  {
    reponse = roll_string(reponse, '#');
    if (state_door == true)
    {
      state_laser2 = true;
      digitalWrite(right_laser, HIGH);
    }
    Serial.print("ACK");
  }
  
  
  
  if (reponse == "DOOR_STAT")
  {
    reponse = roll_string(reponse, '#');
    if (digitalRead(door))
      emet = "True";
    else
      emet = "False";
    Serial.print(emet);
  }
  
  
  
  if (reponse == "LIGH_STAT")
  {
    reponse = roll_string(reponse, '#');
    if (state_light == false)
      Serial.print("False");
    else
      Serial.print("True");
  }
  
  
  
  if (reponse == "LAS1_STAT")
  {
    reponse = roll_string(reponse, '#');
    if (state_laser1 == false)
      Serial.print("False");
    else
      Serial.print("True");
  }
  
  
  
  if (reponse == "LAS2_STAT")
  {
    reponse = roll_string(reponse, '#');
    if (state_laser2 == false)
      Serial.print("False");
    else
      Serial.print("True");
  }
  
  
  
  if (reponse == "POWER_12V")
  {
    reponse = roll_string(reponse, '#');
    tmp = analogRead(a_i_pow12);
    pow12 = map(tmp, 0, 1023, 0, 5000);
    pow12 /= 100;
    Serial.print("120");
  }
  
  
  
  if (reponse == "POWER_05V")
  {
    reponse = roll_string(reponse, '#');
    tmp = analogRead(a_i_pow05);
    pow05 = map(tmp, 0, 1023, 0, 5000);
    pow05 /= 100;
    Serial.print("050");
  }
  
  
  
  if (calib_mpap == true && state_door == true)
  {
    step_motor_turn(1);
    delay(20);
  }
  
  
}


String roll_string(String str, char caract)
{
  str.setCharAt(0,str.charAt(1));
  str.setCharAt(1,str.charAt(2));    
  str.setCharAt(2,str.charAt(3));
  str.setCharAt(3,str.charAt(4));
  str.setCharAt(4,str.charAt(5));
  str.setCharAt(5,str.charAt(6));
  str.setCharAt(6,str.charAt(7));
  str.setCharAt(7,str.charAt(8));
  str.setCharAt(8,caract);
  //Serial.print("\"" + str + "\"\n\r");
  
  return str;
}


void step_motor_config(int mode)
{
  if (mode == 0)
  {
    digitalWrite(MPAP_MS1, LOW); 
    digitalWrite(MPAP_MS2, LOW); 
    digitalWrite(MPAP_MS3, LOW); 
  }
  else if (mode == 1)
  {
    digitalWrite(MPAP_MS1, HIGH); 
    digitalWrite(MPAP_MS2, LOW); 
    digitalWrite(MPAP_MS3, LOW); 
  }
  else if (mode == 2)
  {
    digitalWrite(MPAP_MS1, LOW); 
    digitalWrite(MPAP_MS2, HIGH); 
    digitalWrite(MPAP_MS3, LOW);
  }
  else if (mode == 3)
  {
    digitalWrite(MPAP_MS1, HIGH); 
    digitalWrite(MPAP_MS2, HIGH); 
    digitalWrite(MPAP_MS3, LOW);
  }
  else if (mode == 4)
  {
    digitalWrite(MPAP_MS1, HIGH); 
    digitalWrite(MPAP_MS2, HIGH); 
    digitalWrite(MPAP_MS3, HIGH);
  }
}


void step_motor_turn(int dir)
{
  if (dir == 0)
  {
    digitalWrite(MPAP_DIR, LOW);
  }
  else
  {
    digitalWrite(MPAP_DIR, HIGH);
  }
  
  digitalWrite(MPAP_STEP, HIGH);
  delay(1);
  digitalWrite(MPAP_STEP, LOW);
  delay(1);
}

Fabriquez votre scanner

Bientot

Licence

Logiciel

Le logiciel Pyscan3D est placé sous licence GPL.

_images/GPLv3_Logo.svg_wiki.png

Hardware

Le hardware (hors Arduino) présenté sur ce site est placé sous licence Creative Commons BY-NC-SA.

_images/by-nc-sa.png

Firmware

Le firmware associé à l’Arduino est placé sous licence Creative Commons BY-NC-SA.

_images/by-nc-sa.png