Introduction: DO YOUR OWN SCANNER 3D

Picture of DO YOUR OWN SCANNER 3D

The purpose of this project was to make a scanner:

1. That can scan pieces with maximun dimensions of 25cm in height and 10cm in width.

2. Cost-friendly

3. With the detail list of all materials used

The end result was very successfull and hopefully this instructable will help more people try it and have fun while learning.

YOUTUBE link of a scan:

https://youtu.be/oZjiIRMD-Ho

Step 1: The Hardware

Picture of The Hardware

The hardware of the scanner was made from scratch by machining each of the aluminum pieces used.

Three pieces were also designed specially to be printed in 3D, taking into account the advantages that this process entails in the moment of rapid prototypes.

List of materials in hardware for the Scanner structure:

-Worm gear, Acme 8mm (30cm)

-Bearings 608zz

-4 Bearings Sc8uu

-2 Linear rods (30cm)

-Acrilic Base

-30 Screws M4 15mm

-Metal Screeds

-Rivets and rivet gun

Step 2: 3D Printing Pieces

Picture of 3D Printing Pieces

As mentioned before some of the pieces were made by using a 3D printer to facilitate the process of machining.

We used the software of CAD NX 8.

-The first piece (Top Left piece) was the base for the bearing 608zz. This piece is screwed to the top of the 3D scanner. (base_balero.stp)

-The second piece (Top Right piece) was designed to hold the sensor that will read the object. This piece is screwed to the screw spindle and 4 bolts sc8uu from the rear while the front is screwed the sensor. Being the piece that more geometric constraints presented turned out to be the most complicated to design. Due to variations in the dimensions of the characteristics generated by the machining done to the base of the scanner, within the design of this piece had to counteract these inaccuracies, adding complexity to it. The resulting piece was able to meet all required geometric constraints as well as to place the sensor at a acceptable distance for its work. (base_sensor.stp)

-The third piece (Buttom Left piece) is responsible for holding the rotating disk on which the desired scanned object is placed. This piece also fulfills the function of connecting the step motor with the disc, it was designed with nut holes that allowed it to better fit the piece to the engine shaft. (base_disco.stp)

-The fourth piece (Buttom Right piece) was made in acrilic by a company called Finito Lab. This base is responsible for holding the piece to be scanned. It is 12cm in diameter and has the four holes designed for the step motor to be fitted. (disco.stp)

Step 3: Assembly

Picture of Assembly

For the assembly of the structure we need to join all of our hardware together. First we take our previously machined aluminum slabs. The two largest one will be top and bottom of a rectangular box that will be created once the rivet joins the other medium length slabs mechanically. Once we have the rectangular box and additional large slab should be joined standing just at the back. Now its time to set the 3D printed pieces in our assembly.

First we take the worm and settle it through the spindle. Then the 3D printed piece which is the base for the sensor should be also included in the spindle and joined mechanically with bearing. Bearing will aswell be supporting the two lineal rods. At the top of the spindle another bearing should be set with the second 3D printed piece which is attatched to the aluminum slab.

Once we have done the steps described above its turn to add the stepper motors with screws from the botom just leaving the shaft of the motor upside from the rectangular aluminum box. Then two couplers should be added to each of the stepper motors. One is fixed to the spindle and the other to the round rotating base. Its important to mention that this second couple was also a 3D printed part in order to get it fixed correctly to the rotating base.

In this way there is no more to do other than settling the sensor through the 3D printed sensor base and making the corresponding electronic connections.

Step 4: Electronics

Picture of Electronics

Once the 3D scanner is assembled you are ready to add the electronic components.

The ones used for this scanner are the following:

- Arduino Uno

- CNC Shield Arduino

- 2 Stepper Motor NEMA 17

- Sharp sensor GP2Y0A41SK0F

- 2 Pololu drivers A4988

- Limit switch

- Charger 12V (Power source)

In the picture you can see how the electrical connections where made.

Be careful while placing the drivers in the CNC Shield, make sure the pines are correctly place and located referring to the shield.

Also, remember that is important to connect the motor terminals correctly because this is basic for the direction to where your motors supposed to move.

Step 5: Scanning the Piece

Picture of Scanning the Piece

Now you are ready to scan your first piece.

Through the Arduino platform the connection with the infrarred sensor and the stepper motors was made, data was printed in the Serial Monitor and saved in an text file.

//Declare variables

int scannerValues;

int csPin=10;

int sensePin=A5;

int tStep=2;

int tDir=5;

int tEnable=8;

int push=13;

int zStep=4;

int zDir=7;

int value;

void setup()

{

//Define stepper pins as digital output pins

pinMode(tStep,OUTPUT);

pinMode(tDir,OUTPUT);

pinMode(tEnable,OUTPUT);

pinMode(zStep,OUTPUT);

pinMode(zDir,OUTPUT);

pinMode(push,INPUT);

//Set rotation direction of motors

digitalWrite(tDir,HIGH);

digitalWrite(zDir,HIGH);

//delay(100);

//Enable motor controllers

digitalWrite(tEnable,LOW);

// Open serial communications

Serial.begin(9600);

Serial.println(value);

}

void loop()

{

int vertDistance=10; //Total desired z-axis travel

int noZSteps=150; //No of z-steps per rotation. Distance = noSteps*0.05mm/step

int zCounts=50; //Total number of zCounts until z-axis returns home 15

//int thetaCounts=400;

int thetaCounts=200;

// Scan object

digitalWrite(zDir,HIGH);

delay(5000);

for (int j=0; j

{

for (int i=0; i

{

rotateMotor(tStep, 1); //Rotate theta motor one step

delay(200);

//double senseDistance=0; //Reset senseDistanceVariable;

double senseDistance=readAnalogSensor(); //Read Sharp sensor, calculate distance

}

Serial.print('\n');

rotateMotor(zStep, noZSteps); //Move z carriage up one step

delay(200);

}

// Scan complete. Rotate z-axis back to home and pause.

digitalWrite(zDir,LOW);

delay(10);

for (int j=0; j

{

rotateMotor(zStep, noZSteps);

delay(10);

}

for (int k=0; k<3600; k++) //Pause for one hour (3600 seconds), i.e. freeze until power off because scan is complete.

{

delay(1000);

}

}

//Rotation of platform

void rotateMotor(int pinNo, int steps)

{

for (int i=0; i

{

digitalWrite(pinNo, LOW); //LOW to HIGH changes creates the

delay(1);

digitalWrite(pinNo, HIGH); //"Rising Edge" so that the EasyDriver knows when to step.

delay(1);

//delayMicroseconds(500); // Delay so that motor has time to move

//delay(100); // Delay so that motor has time to move

}

}

//Read sensor data, mean and display

double readAnalogSensor()

{

//int noSamples=10;

int noSamples=100;

int sumOfSamples=0;

float senseValue=0;

float senseDistance=0;

for (int i=0; i

{

senseValue=analogRead(sensePin); //Perform analogRead

delay(2); //Delay to let analogPin settle -- not sure if necessary

sumOfSamples=sumOfSamples+senseValue; //Running sum of sensed distances

}

senseValue=sumOfSamples/noSamples; //Calculate mean

senseDistance=senseValue; //Convert to double

senseDistance=mapDouble(senseDistance,0.0,1023.0,0.0,5.0); //Convert analog pin reading to voltage

//Serial.print("Voltage: "); //Debug

//Serial.println(senseDistance); //Debug

//Serial.print(" | "); //Debug

//Sharp sensor GP2Y0A41SK0F calibration formula

senseDistance= -176.88*pow(senseDistance,6)+1154.3*pow(senseDistance,5)-3059.7*pow(senseDistance,4)+4197.3*pow(senseDistance,3)-3113.1*pow(senseDistance,2)+1149.8*senseDistance-139.8; //Convert voltage to distance in cm via cubic fit of Sharp sensor datasheet calibration

//Print to Serial - Debug

//Serial.print("Distance: "); //Debug

//Serial.println(senseDistance); //Debug

//Serial.print(senseValue);

//Serial.print(" | ");

Serial.print(senseDistance);

Serial.print("\t");

return senseDistance;

}

double mapDouble(double x, double in_min, double in_max, double out_min, double out_max)

{

return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

}

Step 6: 3D Plotting

Picture of 3D Plotting

The data printed in the Serial Monitor has to be copy and paste in a text file that is going to be used in a Matlab program that take cares of plotting the 3D graph corresponding to the scanned piece.

Remember to change the name of the .txt in the Matlab program in order for you to 3D plot the correct data.

Here is the Matlab program:

//Declare variables

int scannerValues;

int csPin=10;

int sensePin=A5;

int tStep=2;

int tDir=5;

int tEnable=8;

int push=13;

int zStep=4;

int zDir=7;

int value;

void setup()

{

//Define stepper pins as digital output pins

pinMode(tStep,OUTPUT);

pinMode(tDir,OUTPUT);

pinMode(tEnable,OUTPUT);

pinMode(zStep,OUTPUT);

pinMode(zDir,OUTPUT);

pinMode(push,INPUT);

//Set rotation direction of motors

digitalWrite(tDir,HIGH);

digitalWrite(zDir,HIGH);

//delay(100);

//Enable motor controllers

digitalWrite(tEnable,LOW);

// Open serial communications

Serial.begin(9600);

Serial.println(value);

}

void loop()

{

int vertDistance=10; //Total desired z-axis travel

int noZSteps=150; //No of z-steps per rotation. Distance = noSteps*0.05mm/step

int zCounts=50; //Total number of zCounts until z-axis returns home 15

//int thetaCounts=400;

int thetaCounts=200;

// Scan object

digitalWrite(zDir,HIGH);

delay(5000);

for (int j=0; j

{

for (int i=0; i

{

rotateMotor(tStep, 1); //Rotate theta motor one step

delay(200);

//double senseDistance=0; //Reset senseDistanceVariable;

double senseDistance=readAnalogSensor(); //Read Sharp sensor, calculate distance

}

Serial.print('\n');

rotateMotor(zStep, noZSteps); //Move z carriage up one step

delay(200);

}

// Scan complete. Rotate z-axis back to home and pause.

digitalWrite(zDir,LOW);

delay(10);

for (int j=0; j

{

rotateMotor(zStep, noZSteps);

delay(10);

}

for (int k=0; k<3600; k++) //Pause for one hour (3600 seconds), i.e. freeze until power off because scan is complete.

{

delay(1000);

}

}

//Rotation of platform

void rotateMotor(int pinNo, int steps)

{

for (int i=0; i

{

digitalWrite(pinNo, LOW); //LOW to HIGH changes creates the

delay(1);

digitalWrite(pinNo, HIGH); //"Rising Edge" so that the EasyDriver knows when to step.

delay(1);

//delayMicroseconds(500); // Delay so that motor has time to move

//delay(100); // Delay so that motor has time to move

}

}

//Read sensor data, mean and display

double readAnalogSensor()

{

//int noSamples=10;

int noSamples=100;

int sumOfSamples=0;

float senseValue=0;

float senseDistance=0;

for (int i=0; i

{

senseValue=analogRead(sensePin); //Perform analogRead

delay(2); //Delay to let analogPin settle -- not sure if necessary

sumOfSamples=sumOfSamples+senseValue; //Running sum of sensed distances

}

senseValue=sumOfSamples/noSamples; //Calculate mean

senseDistance=senseValue; //Convert to double

senseDistance=mapDouble(senseDistance,0.0,1023.0,0.0,5.0); //Convert analog pin reading to voltage

//Serial.print("Voltage: "); //Debug

//Serial.println(senseDistance); //Debug

//Serial.print(" | "); //Debug

//Sharp sensor GP2Y0A41SK0F calibration formula

senseDistance= -176.88*pow(senseDistance,6)+1154.3*pow(senseDistance,5)-3059.7*pow(senseDistance,4)+4197.3*pow(senseDistance,3)-3113.1*pow(senseDistance,2)+1149.8*senseDistance-139.8; //Convert voltage to distance in cm via cubic fit of Sharp sensor datasheet calibration

//Print to Serial - Debug

//Serial.print("Distance: "); //Debug

//Serial.println(senseDistance); //Debug

//Serial.print(senseValue);

//Serial.print(" | ");

Serial.print(senseDistance);

Serial.print("\t");

return senseDistance;

}

double mapDouble(double x, double in_min, double in_max, double out_min, double out_max)

{

return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

}

El programa de Arduino es cargado, este se tarda aproximadamente 40 minutos para que termine de escanear una pieza para continuar a copiar los datos obtenidos del escaneo del monitor serial y guardarlos en un archivo de texto “positions#.txt” para su próximo despliegue en gráfica con el programa de Matlab.

Programación Matlab

clear all;

clc;

%% Processing variables

maxDistance=18.5; %Upper limit -- raw scan value only scanning "air"

minDistance=3.5; %Lower limit -- raw scan value error: reporting negative reading

midThreshUpper=0.001; %Offset radius threshold around 0

midThreshLower=-midThreshUpper; %Offset radius threshold around 0

windowSize=3; %Window size for average filter to clean up mesh

interpRes=1; %Interpolation resolution, i.e. keep every interRes-th row

centerDistance=11.5;%9.2; %[cm] - Distance from scanner to center of turntable

zDelta=0.25;

rawData=importdata('positions4.txt');

num=numel(rawData);

disp(num);

rawData(rawData<0)=0; %Remove erroneous scans from raw data

%rawData(rawData=='X')=[];

%disp(rawData);

indeces=numel(rawData);

disp(indeces);

%indeces1=200*40*20; %Find indeces of '9999' delimiter in text file, indicating end of z-height scan

%disp(indeces1);

% Arrange into matrix, where each row corresponds to one z-height.

column=50;

%cont=indeces/column;

rawData(1,:)=[];

r=reshape(rawData,[200,column]);

r=r';

disp(r);

r(r>maxDistance)=NaN; %Remove scan values greater than maxDistance;

r(r

r=centerDistance-r; %Offset scan so that distance is with respect to turntable center of rotation

r=abs(r);

%disp('r');

%disp(r);

%Remove scan values around 0

midThreshUpperIdx=r>midThreshLower;

midThreshLowerIdx=r

midThreshIdx=midThreshUpperIdx.*midThreshLowerIdx;

r(midThreshIdx==1)=NaN;

% Create theta matrix with the same size as r -- each column in r corresponds to specific orientation

%theta=0:360/size(r,2):360;

theta=360:-360/size(r,2):0;

theta(end)=[];

theta=repmat(theta,[size(r,1) 1]);

%disp('theta');

%disp(theta);

theta=theta*pi/180; %Convert to radians

% Create z-height array where each row corresponds to one z-height

z=0:zDelta:size(r,1)*zDelta;

z(end)=[];

z=z';

z=repmat(z,[1,size(r,2)]);

%disp('r');

%disp(r);

[x,y,z]=pol2cart(theta,r,z); %Convert to cartesian coordinates

%disp('x');

%disp(x);

%disp('y');

%disp(y);

%disp('z');

%disp(z);

%Replace NaN values in x, y with nearest neighbor at the same height

for i=1:1:size(x,1)

if sum(isnan(x(i,:)))==size(x,2)

x(i:end,:)=[];

y(i:end,:)=[];

z(i:end,:)=[];

break;

end

end

for i=1:1:size(x,1)

latestValueIdx=find(~isnan(x(i,:)),1,'first');

latestX=x(i,latestValueIdx);

latestY=y(i,latestValueIdx);

for j=1:1:size(x,2)

if isnan(x(i,j))==0

latestX=x(i,j);

latestY=y(i,j);

else

x(i,j)=latestX;

y(i,j)=latestY;

end

end

end

%Resample array based on desired mesh resolution

interpIdx=1:interpRes:size(x,1);

xInterp=x(interpIdx,:);

yInterp=y(interpIdx,:);

zInterp=z(interpIdx,:);

%Smoothe data to eliminate more noise

h=fspecial('average',windowSize); %Define average filter

%h=fspecial('gaussian',10,1.25); %Define gaussian filter

xInterp=padarray(xInterp,[0, windowSize],'symmetric'); %Add symmetric duplicate padding along rows to correctly filter array edges

yInterp=padarray(yInterp,[0, windowSize],'symmetric'); %Add symmetric duplicate padding along rows to correctly filter array edges

% xInterp=filter2(h,xInterp); %Filter x

% yInterp=filter2(h,yInterp); %Filter y

xInterp=xInterp(:,windowSize:end-windowSize-1); %Remove padding

yInterp=yInterp(:,windowSize:end-windowSize-1); %Remove padding

%Force scan to wrap by duplicating first column values at end of arrays

xInterp(:,end)=xInterp(:,1);

yInterp(:,end)=yInterp(:,1);

zInterp(:,end)=zInterp(:,1);

%Add top to close shape

xTop=mean(xInterp(end,:));

yTop=mean(yInterp(end,:));

xInterp(end+1,:)=xTop;

yInterp(end+1,:)=yTop;

zInterp(end+1,:)=zInterp(end,1)-zInterp(end-1,1)+zInterp(end,1);

%surf(xInterp,yInterp,zInterp); %Plot point cloud as a mesh to verify that processing is correct

plot3(xInterp,yInterp,zInterp,'.b'); %Plot point cloud as a mesh to verify that processing is correct

Comments

ShyamM11 (author)2017-03-08

can I have YouTube link

nandohaze (author)2016-12-03

there is the possibility to replicate this tutorial but in a bigger size? like to scan larger objets?

blic19933 (author)2016-12-01

Good tutorial. While this is an nice exercise in creating a DIY scanner if you have the resources to do machining and access to Matlab you can surely afford a commercial scanner. A true low cost design would use easily worked materials and open source freeware.

Swansong (author)2016-11-30

Awesome project! I'd love to make a 3d scanner so I could copy and make more miniatures. :)