Introduction: Let's Cook: 3D Scanner Based on Arduino and Processing
Ever wonder of copypaste method, usable in real life, not only in virtual reality? Me too. It is 21th century, and those are nearer than most of all can imagine! This is possible thank to 3d scanners and printers. So let's try to scan something!
First of all, sorry for my English skills. It is not my native language, I have learned it at school, but don't have many occasions to use it, except of reading articles in English. But i hope it would be good enough to understand.
Step 1: Ingredients
-one Arduino with Arduino IDE
-one Processing IDE
-a lot of LEGO (best toy ever!)
-one stepper motor
-one Stepper motor driver and power supply
-one linear laser
-one webcam
- one working Meshlab
and Some help :)
First, you need to get all parts and think about overall look and working method.
And it depends the most of type of stepper motor you can get. I got my stepper from old OKI printer, which has attached gear set. It was very useful, because i could attach Lego pulley, without destroying it permanently. In a fact, i hadn't destroyed any Lego blocks during build of rotating platform. I hate destroying things.
Code is primitive, i know it. It has major mistakes, not all needed algorithms are applied. But it generates point clouds, which are very similar to real things and that was goal of this alpha version of scanner.
So let's prepare parts.
Step 2: Principle of Operation
How does it work?
We have to found Cartesian coordinates (in some space) of points which belongs to scanned object.
Basically, we are looking for distance, between rotation axle and a point marked red by laser ("ro" on the picture). To found this, we have to measure how many pixels are between optical axle of camera and laser-marked point. On picture, this distance is marked as "b". When we get that information, we have to convert it into millimeter (how many pixels are in one millimeter). Angle between laser and camera axle is constant and equals "alpha". Using simple trigonometry, we can calculate "ro":
sinus(alpha) = b / ro, which means that ro = b / sinus(alpha)
This operation repeats every layer, in my case it is 480times. Then rotating platforms move by some angle and whole operation repeats.
Let's move to second picture.
Previous operations gave us coordinates in polar coordinates system. In polar system, every point look something like that:
P = (distance from Z axis, angle between point and X axis, Z) which is P = (ro, fi, z).
Ro is our distance, measured in previous operation. Fi is an angle of rotating platform. It grows an constant amount, every time platform rotate. This constant amount in equal 360 degree / number of operation
I.e. for 120 profiles around object, platform moves about 360deg / 120 = 3 deg. So after first move, fi = 3, after second fi = 6, after third fi = 9 etc.
Z value is the same value as Z in Cartesian system.
Conversion from polar to Cartesian is very simple:
x = ro * cosinus( fi )
y = ro * sinus( fi )
z = z
Step 3: Motor
It is 4-connector bipolar stepper motor from an old OKI printer. It has 48 steps per revolution (7,5 deg per step), driven by 3,7V power supply. Integrated gear has 6:1 ratio, which means i had 6*48 steps on the output. It takes 200-250mA when moving.
I soldered 4 wires to connectors of stepper motor. To another ends of wires i have soldered single gold pin. Now it is very easy to connect it with driver.
I attached Lego pulley to the integrated gear. I took it out and drill 6 holes. Holes has same size and arrangement as holes on Lego pulley. Pulley and gear are joined together with „3-long” shafts.
Step 4: Motor Driver and Power Supply
Driver:
Bipolar stepper is driven by h-bridge. Because of low power consumption of stepper, L293D is more than enough. In simplest variant, h-bridge uses 4 digital output pins from Arduino, +5V and GND. For reducing output pins to 2, You can use small, additional board.
More info can be found in Arduino reference and on Tom's Igoe page: http://www.tigoe.com/pcomp/code/circuits/motors/stepper-motors/
Ready to termotransfer boards and schematics are attached below.
Parts:
Part Value Package Library Position (mil) Orientation
IC1 L293D DIL16 st-microelectronics (700 750) R270
JP1 2X03 pinhead (700 1350) R0
JP2 2X03 pinhead (700 1875) R0
OUT1 AK500/2-H con-ptr500 (300 150) R0
OUT2 AK500/2-H con-ptr500 (1100 150) R0
POWER 1X02 pinhead (950 1875) R90
Q1 BC547 TO92 transistor-npn (275 2175) R180
Q2 BC547 TO92 transistor-npn (1125 2175) R180
R1 1k 0207/10 rcl (425 1700) R0
R2 1k 0207/10 rcl (750 2175) R180
R3 10k 0207/10 rcl (975 1700) R0
R4 10k 0207/10 rcl (800 2050) R0
SIGNAL 1X02 pinhead (450 1875) R90
X3 AK500/2-H con-ptr500 (700 150) R0
Power supply:
Power supply for stepper is super-simple LM317 application. Schematic can be found on datasheet. Using potentiometer, i can set voltage to needed level (3,7V in my case).
Parts:
Part Value Package Library Position (mm) Orientation
C1 100uF E5-8,5 rcl (24.13 5.08) R270
C2 100nF C050-030X075 rcl (10.16 13.335) R270
IC1 317TS v-reg (20.32 16.51) R0
R1 240R 0207/10 rcl (17.78 10.16) R0
R2 5k CA6V pot (13.97 4.445) R180
X1 AK500/2 con-ptr500 (3.81 13.335) R270
X2 AK500/2 con-ptr500 (33.02 13.335) R90
Step 5: Rotating Platform
Lego part! My stepper was cased by Lego, so it has no chance to move. I didn't need to use screws, glue, etc. Momentum is transferred to platform by rubber band and pulley same size as the one on motor (ratio 1:1). Then it is connected with another axle by 20:12 gear set. All of this causes overall ratio 10:1, which means i need exact 10 full revs. of steeper for one revolution of platform. Platform was made of CD glued to broken Lego wheel (it was very poor series of Lego elements, thin plastic causes A LOTS OF brokes during normal play...). CD is covered by green paper, glued by 2-side sticky tape.
Pic is worth more than thousand words?
Step 6: Webcam
I used very primitive Creative Webcam Vista. It's rather old, it has poor sensor (640x480), it has poor optics (plastic lenses). But it has one advantage. I have already had it. It is also attached to rotating platform (little to low, need to change that soon).
Step 7: Linear Laser
Poor quality (~1$) laser pointer is attached to cylindrical lens made from glass rod. This kind of glass rods are used in chemistry labs. Laser and lens is cased in Lego case (cased in case; thank you Captain Obvious...). Laser is turned on by rotating it a little bit, button is pushed by Lego. Also attached to platform. Angle between optical axle of camera and laser are around 30degree.
Step 8: Arduino + IDE
I owe Arduino Leonardo only, so it was only choice i've got.
It has simple code, which causes rotate stepper when got command from Procesing. Commands are sent by Serial.
I chose 4 steps per phase, which means i got 120 photos and 120 profiles around object, every 12 degree. Less steps causes mistakes because of elasticity of rubber band.
It is using arduino's standard stepper library.
code:
#include <Stepper.h>
Stepper oki(48,8,9); //see stepper tutorial in arduino.cc for info about that
const int ledPin = 13; // the pin that the LED is attached to
int incomingByte; // a variable to read incoming serial data into
void setup() {
// initialize serial communication:
Serial.begin(9600);
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
oki.setSpeed(60);
}
void loop() {
// see if there's incoming serial data:
if (Serial.available() > 0) {
// read the oldest byte in the serial buffer:
incomingByte = Serial.read();
// if it's a capital H (ASCII 72), turn on the LED:
if (incomingByte == 'S') {
digitalWrite(ledPin, HIGH);
oki.step(4);
}
// if it's an L (ASCII 76) turn off the LED:
if (incomingByte == 'K') {
digitalWrite(ledPin, LOW);
}
}
}
Step 9: Processing
Why Processing? Because it is easy to use, with big reference and tutorial base. Also it is very similar to arduino. That means the probability of mistake during code writing decrease. Libraries are well documented also.
First thing to do in processing is installation of GSVideo library. Download and installation instructions are there: http://gsvideo.sourceforge.net/
So basically program sequence looks something like that, but it is divided into 2 loops (make photos and the rest):
make photo => find brightest pixel in every row => save picture of representation brightest pixels => find distance between middle of picture and brightest pixel in every row => convert gathered polar coordinates to kartesian XYZ => save ASC file with point cloud.
Explanation can be found in comments in code.
First thing must be done preety soon is setting where Z-value is equal 0. Now Z=0 is set not on the center of platform, but on the first row of photo. This causes that output point cloud is upside-down.
code:
import codeanticode.gsvideo.*;
import processing.serial.*;
//objects
PFont f;
GSCapture cam;
Serial myPort;
PrintWriter output;
//colors
color black=color(0);
color white=color(255);
//variables
int itr; //iteration
float pixBright;
float maxBright=0;
int maxBrightPos=0;
int prevMaxBrightPos;
int cntr=1;
int row;
int col;
//scanner parameters
float odl = 210; //distance between webcam and turning axle, [milimeter], not used yet
float etap = 120; //number of phases profiling per revolution
float katLaser = 25*PI/180; //angle between laser and camera [radian]
float katOperacji=2*PI/etap; //angle between 2 profiles [radian]
//coordinates
float x, y, z; //cartesian cords., [milimeter]
float ro; //first of polar coordinate, [milimeter]
float fi; //second of polar coordinate, [radian]
float b; //distance between brightest pixel and middle of photo [pixel]
float pxmmpoz = 5; //pixels per milimeter horizontally 1px=0.2mm
float pxmmpion = 5; //pixels per milimeter vertically 1px=0.2mm
//================= CONFIG ===================
void setup() {
size(800, 600);
strokeWeight(1);
smooth();
background(0);
//fonts
f=createFont("Arial",16,true);
//camera conf.
String[] avcams=GSCapture.list();
if (avcams.length==0){
println("There are no cameras available for capture.");
textFont(f,12);
fill(255,0,0);
text("Camera not ready",680,32);
}
else{
println("Available cameras:");
for (int i = 0; i < avcams.length; i++) {
println(avcams[i]);
}
textFont(f,12);
fill(0,255,0);
text("Camera ready",680,32);
cam=new GSCapture(this, 640, 480,avcams[0]);
cam.start();
}
//Serial (COM) conf.
println(Serial.list());
myPort=new Serial(this, Serial.list()[0], 9600);
//output file
output=createWriter("skan.asc"); //plik wynikowy *.asc
}
//============== MAIN PROGRAM =================
void draw() {
PImage zdjecie=createImage(cam.width,cam.height,RGB);
cam.read();
delay(2000);
for (itr=0;itr<etap;itr++) {
cam.read();
zdjecie.loadPixels();
cam.loadPixels();
for (int n=0;n<zdjecie.width*zdjecie.height;n++){
zdjecie.pixels[n]=cam.pixels[n];
}
zdjecie.updatePixels();
set(20,20,cam);
String nazwaPliku="zdjecie-"+nf(itr+1, 3)+".png";
zdjecie.save(nazwaPliku);
obroc();
delay(500);
}
obroc();
licz();
noLoop();
}
void licz(){
for (itr=0; itr<etap; itr++){
String nazwaPliku="zdjecie-"+nf(itr+1, 3)+".png";
PImage skan=loadImage(nazwaPliku);
String nazwaPliku2="odzw-"+nf(itr+1, 3)+".png";
PImage odwz=createImage(skan.width, skan.height, RGB);
skan.loadPixels();
odwz.loadPixels();
int currentPos;
fi=itr*katOperacji;
println(fi);
for(row=0; row<skan.height; row++){ //starting row analysis
maxBrightPos=0;
maxBright=0;
for(col=0; col<skan.width; col++){
currentPos = row * skan.width + col;
pixBright=brightness(skan.pixels[currentPos]);
if(pixBright>maxBright){
maxBright=pixBright;
maxBrightPos=currentPos;
}
odwz.pixels[currentPos]=black; //setting all pixels black
}
odwz.pixels[maxBrightPos]=white; //setting brightest pixel white
b=((maxBrightPos+1-row*skan.width)-skan.width/2)/pxmmpoz;
ro=b/sin(katLaser);
//output.println(b + ", " + prevMaxBrightPos + ", " + maxBrightPos); //I used this for debugging
x=ro * cos(fi); //changing polar coords to kartesian
y=ro * sin(fi);
z=row/pxmmpion;
if( (ro>=-30) && (ro<=60) ){ //printing coordinates
output.println(x + "," + y + "," + z);
}
}//end of row analysis
odwz.updatePixels();
odwz.save(nazwaPliku2);
}
output.flush();
output.close();
}
void obroc() { //sending command to turn
myPort.write('S');
delay(50);
myPort.write('K');
}
Step 10: Scanning
Turn on the power supply, turn on the laser, hit Run in Processing IDE. Wait till scanning is ready. You will get *.asc file, which contains Cartesian coordinates of every point.
Step 11: Point Cloud
Download Meshlab (http://meshlab.sourceforge.net/) or use some other software to manage 3D point clouds. Import your *.asc file, simple by drag and drop method. Uncheck triangulation and hit OK. You will get see cloud points of scanned object! Success!
I cannot do almost anything more in Meshlab, because it is crashing a lot. Don't know why, I'll be fighting with this. But if you get stable version (is there any?) you can turn cloud into solid and exporting it as stereolitography *.stl file. And this can be printed on any 3D printer!
Isn't it lovely?
Edit: 4. december 2012:
I was pretty sure i've attached output file to this step.
It seems I cannot add asc or 7z file to the instructable...
If you want to take a look at asc output file, please download pdf attached to this step and delete ".pdf" file extension from the name of file. You should to remain "owl.asc" only.
This scan is not the same scan showed in instructable! It was taken yesterday during day, so it is little distorted! It was control scan made after minor modification of camera mount.
Attachments
Step 12: Fighting With Meshlab
As I said earlier, i've go a lot of meshlab crashes. But I tried to fought with them, and I got partially success. I've made another scan, and did some mid-quality solid of it! I don't think i can get more accuracy using my webcam.
But hey! It looks like an OWL!
I've made something like that:
- Filters => Remeshing... => Surface reconstruction: Poisson; attributes 10, 8, 1, 1 (it is quite possible you will have to experiment with another values)
- Filters => Normals... => Invert face orientation
- Filters => Smoothing... => Taubin Smooth
- Filters => Vertex attribute transfer; mark "transfer geometry", "transfer normals"; source "another owl - good quality.asc"; target "poisson mesh"
Step 13: To-do and Ideas
To-do:
setting Z=0 position.
Rebuild interface (actually: build... ;) )
Buy real linear laser module, without „ghost light” effect
add calibration to main program
add an algorithms that makes scan more accurate
and a lot, lot of other things...
Ideas:
better webcam, fullHD maybe?
Maybe DSLR instead of webcam?
Turning webcam 90degree will increase vertical resolution from 480 to 640 layers
Linux compatibile (currently I got errors installing gsvideo on my mint13)
Far, far away idea:
Mobile phone scanner. Almost every mid- and premium-class smartphone has very good quality camera. Android software shouldn't be so hard to write. Arduino Mega ADK can be directly operated by android phone... Unfortunately I don't have smartphone neither Mega ADK. When I'll get that, I will try.
I hope You will like my instructable. Scan things and have fun with that!
cube.

Participated in the
Instructables Design Competition
61 Comments
10 years ago on Introduction
I had an Arduino lying on my desk for a while, a friend got into 3D printing, another one was over motivated and we finally bumped into your Instructables. A few hours later, without an installation as stable as yours and a cheap equipment (the good point of living in China at the moment) we already get some decent result (picture attached).
Now the next step is to deal with meshlab, because even with a nice cloud of points it doesn't seem so easy to get a nice STL, I may try the library that Amanda was suggesting actually (crazy Instructables Amada also did by the way!)
And then, plug that in a 3D printer \o/
Thank you so much for the inspiration!!
Reply 8 years ago on Introduction
Hi... could make one instructables for us (the noob)... we will appreciate that.
Reply 8 years ago on Introduction
Hi,
Hum Idrispo I'm confused, here is the page of an instructable about this so what else are you asking for? You mean this instructables is not detailed enough?
Reply 10 years ago on Introduction
Good to see that my instructable was used by someone!
Currently I have unplanned stop at the scanner. Parts for this project (laser) and for another one, ordered in China, have huge delay... My hands shaking, wants to do sthg...
10 years ago on Introduction
have you tried sxporting the stl directly from processing, I just downloaded the modelbuilder library and have had a lot of success with it. cool project!
Reply 10 years ago on Introduction
I have not because I hadn't know about it. It seems to be really promising thing! Thank You very much for this information!
Reply 10 years ago on Introduction
http://workshop.evolutionzone.com/2011/04/06/code-modelbuilder-library-public-release/
it's really easy to use
6 years ago
hello dear than you for your project really its amazing project,
Dear i use the following code for stepper motor i dont know its the same or not becaue your arduino code dont work in my IDE please i need your help :
#include <Stepper.h>
/*-----( Declare Constants, Pin Numbers )-----*/
//---( Number of steps per revolution of INTERNAL motor in 4-step mode )---
#define STEPS_PER_MOTOR_REVOLUTION 48
//---( Steps per OUTPUT SHAFT of gear reduction )---
#define STEPS_PER_OUTPUT_REVOLUTION 48 * 6 //288
const int ledPin = 12;
int incomingByte; // a variable to read incoming serial data into
Stepper small_stepper(STEPS_PER_MOTOR_REVOLUTION, 8, 10, 9, 11);
/*-----( Declare Variables )-----*/
int Steps2Take;
void setup() /*----( SETUP: RUNS ONCE )----*/
{
// initialize serial communication:
Serial.begin(9600);
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// Nothing (Stepper Library sets pins as outputs)
}/*--(end setup )---*/
void loop() /*----( LOOP: RUNS CONSTANTLY )----*/
{
incomingByte = Serial.read();
// if it’s a capital H (ASCII 72), turn on the LED:
Steps2Take = - STEPS_PER_OUTPUT_REVOLUTION; // Rotate CCW 1 turn
small_stepper.setSpeed(60); // 700 a good max speed??
small_stepper.step(4);
if (incomingByte == 'S')
{
digitalWrite(ledPin, HIGH);
// delay(2000);
}
else if (incomingByte == 'E')
{
//return ;
digitalWrite(ledPin, LOW);
}
/* --(end main loop )-- */
}
/* ( THE END ) */
8 years ago on Introduction
Hi, i made the connections as per your instruction but when i plug it on my motor keep buzzing(shaft not rotating) please help me as soon as possible.
thanks.
hafzalhamza@gmail.com
8 years ago on Introduction
Great project. I am new to Processing, can you please help me in understanding the code, the "configuration part" specially. Please I am stuck at this part and I couldn't figure it out myself.
8 years ago on Introduction
Your project is really very Interesting. Can You Please explain me the "configuration section" and "main section" of your code. I am trying to make this project at home but since I am new to processing I want your help.
8 years ago on Introduction
Hi. Built my 3d scanner based from your build. But I created my own communication protocol with my teensy. Letting the teensy turn on/off laser, stepper driver, set microstepping and of course stepping the motor.
The first problem i ran into with your processing code was to use GSvideo. Seems like I cant install the library. Processing wont detect it. So I went with the default processing.video.*.
With processing.video.* "cam = new Capture(this, 1024, 768, avcams[0]) wont compile.
With "cam = new Caputer(this, 1024, 768); the built in camera is selected and not my usb one.
With "cam = new Capture(this, avcams[0]); Correct camera is selected but I "get: java.lang.IllegalArgumentException: Width (0) and height (0) cannot be <= 0" which refers to cam.height and cam.width. So no images are captured.
Turned out to be a long trouble shooting post. Really looking forward to getting the scanner to work. Hope you can help me.
Great project, thumbs up!!
8 years ago
"hey i tried running the code on processing but i got an error saying ArraryIndexOutofBoundsException: 0 for this line
myPort=new Serial(this, Serial.list()[0], 9600);"
I'm having the same problem. Anybody here konws solve it ? Thanks...
9 years ago on Introduction
hey i tried running the code on processing but i got an error saying ArraryIndexOutofBoundsException: 0 for this line
myPort=new Serial(this, Serial.list()[0], 9600);
is there any1 that can help me with this problem?
Reply 8 years ago
I need help too.... Anybody here knows solve this problem? thanks.
8 years ago on Introduction
hi
it all works fine its just when i run the processing
sketch the window that opens up for the camera view is all white is it
possible i could get some help with this
thank you
9 years ago on Step 13
Hi there, great project!
How do you time the picture with the stepper motor? Is that controlled by Arduino or is it just a timing thing?
9 years ago on Step 10
what software to use capture image.?
Reply 9 years ago on Step 10
Processing with Gsvideo library.
Reply 9 years ago on Step 10
without software, and where file *.asc directory save.?
sorry my language not good :)