chris m
Published © GPL3+

3d Laser Scanner

3d Laser scanner to scan areas. Uses 3d printed parts, BBG or BBB and USB camera to generate a point cloud file.

IntermediateWork in progressOver 3 days73
3d Laser Scanner

Things used in this project

Hardware components

MI5100 5MP USB Camera Module
×1

Hand tools and fabrication machines

robo3d

Story

Read more

Custom parts and enclosures

3d printer files

stp file of assemble

stl files for 3d printer

watch scaling mm vs in

Schematics

point cloud .xyz file

point cloud ascii file

BBG Schematic

not the same on BBB, and the laser power supply is not shown.

Project Notes

BB set up notes

Code

3d Scanner version 1.py

Python
1st version of 3d scanner python script. less camera resolution faster scans but less accurate.
need to calibrate tilt angle, spacing, etc to get distances correct. need to change distance to center to get centering loop to work
# by chris martin 6/25/2020
# Uses bits and pieces from the internet
# Python Script is for Python 2.7.16
# test on beaglebone bla

from Stepper_Motor_Control import motorcontrol  #motor class 
from PIL import Image # not really needed, but does help finding laser dot when setting up pixel locations
import select # Not sure what features this library all has
import time # maybe better to use Gevent. eventually
import v4l2capture # for usb camera
import subprocess #allows command line to be used in the program
import math #math and trig functions

# usb camera code http://www.elpcctv.com/mi5100-5mp-usb-camera-module-usb20-aptina-125inch-color-cmos-sensor-100degree-lens-p-221.html

video = v4l2capture.Video_device("/dev/video0") # access usb camera # Open the video device.

subprocess.check_call("v4l2-ctl -c exposure_absolute=150",shell=True) # dim exposure to reduce false positives defualt for camera is 500 
subprocess.check_call("v4l2-ctl -c exposure_auto=1",shell=True) # dim exposure to reduce false positives

# Suggest an image size to the device. The device may choose and
# return another size if it doesn't support the suggested one.

Pixle_row_number=542 #grabbed from image  need to make calibration routine.
H_resolution = 1600  #camera horizontal resolution
V_resolution = 1200  #camera vertical resolution
H_max_resolution = 2592 # elp 5 megapixle camera
V_max_resolution = 1944 # elp 5 megapixle camera
                        # 2592x1944 @ 15fps MJPEG Fails 
                        # 2048x1536 @ 15fps MJPEG Works slower center is 690
                        # 1600x1200 @ 15fps MJPEG Works
                        # 1280x1024 @ 15fps MJPEG
                        # 1920x1080 @ 30fps MJPEG 
                        # 1280x720  @ 30fps MJPEG
                        # 800x600   @ 30fps MJPEG center is 265
                        # 640x480   @ 30fps MJPEG
size_base_pixel = 0.0022 #mm from specs
h_sensor = H_max_resolution*size_base_pixel   #sensor width
w_sensor = V_max_resolution*size_base_pixel   #sensor height
f_camera = 3 #mm focal length
angle_tilt_degrees=50.90 # 3d model is 50 degrees use this to tune distance measurement
angle_tilt_rad=math.radians(angle_tilt_degrees) # convert tilt angle to radians for trig functions
Spacing=320.0 #mm between laser and camera center to pivot axis intersection longer than camera focus 
centerdistancemeasured= 208.3  # measure this value using distance calibration program 208.5 for 2048x1536 210 for 800x600 209-210 for 1600x1200
size_x, size_y = video.set_format( H_resolution,V_resolution,0) #get actual camera set up resolution
H_pixle_width=h_sensor/size_x #lower resolutions group pixles one pixle
V_pixle_width=w_sensor/size_y #lower resolutions group pixles one pixle
#camera data set up

print "size:", size_x, size_y # display resolution
# Create a buffer to store image data in. This must be done before
# calling 'start' if v4l2capture is compiled with libv4l2. Otherwise
# raises IOError.
video.create_buffers(1)
# Start the device. This lights the LED if it's a camera that has one.
video.start()
# Send the buffer to the device.
video.queue_all_buffers()
# Wait a little. Some cameras take a few seconds to get bright enough.
time.sleep(.2)
print "camera set up"
# End Camera and scanner setup 


# create stepper motor control

mc=motorcontrol() # mc stepper motor 
freq=500 #pwm frequency

centeringrevs=.002
Nstepsperrev=200.0 # physical steps per rev 1.8 deg stepper motor tested at 200

m1=1
m2=1
        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
if m1==0 and m2 == 0:
    multipier=1
if m1==1 and m2 == 0:
    multipier=4  
if m1==0 and m2 == 1:
    multipier=16
if m1==1 and m2 == 1:
    multiplier=32    

mc.armspin(0,1,freq,m1,m2,100) # lock arm duty=100
mc.zaxis(0,1,freq,m1,m2,100) #lock zaxis duty=100

# Find center of scanner base does not work the best, but is somewhat repeatable, would be better to have encoder
# program looks for laser point, counts how many pixles wide it is and calcs the average (center)
# the distance is measured many time and compared with actual center distance to pivot to determine center of scanner
# after center is found the arm, is rotated to horizontal
centerfound = 0 #intialize center finding loop
retry=0 # each measurement is taken multiple times
maxretries=5 # each measurement is taken multiple times

while not centerfound: #Loop to find center of base
    mc.armspin(0,1,freq,m1,m2,100) # set arm pwm to 100 duty cycle locking it in place
    select.select((video,), (), (), 1)  #wait for camera to fill buffer
    image_data = video.read_and_queue()  #move camera buffer to program buffer
    done = False # initialize inner loop (look for laser points)
    i=size_x # initialize iterator
    firstpoint=0 #intialize variable 
    numpointsfound = 0 #initialize variable
  
    while not done:
        i=i-1
        # read raw r,g,b
        #0,0 1,0.......799,0
        #.
        #
        #599,0 599,1.....599,799
        Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
        rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
        rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
        rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
        if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
           if firstpoint == 0: #find first pixle that is green
               firstpoint =i
           numpointsfound += 1 # count green pixels found
        if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
            done=True
    centerpoint =(firstpoint+firstpoint+numpointsfound)/2 #average location of green pixles found 
    sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera) # angle between line from pixle average to focus and line from focus to center of sensor
    distance=math.tan(angle_tilt_rad+sigma)*Spacing # calulate distance from pivot plane to laser point
    distancetocenter=math.sqrt(distance*distance+((Spacing/2)*(Spacing/2))) # calulate distance from pivot to laser point
    print "distance to center :",distancetocenter
    if distancetocenter >=(centerdistancemeasured) and distancetocenter<(centerdistancemeasured+1): #look for center
        print "centered" 
        centerfound=1
    else:
        if retry >=maxretries: # rotate arm one step if not found
            mc.armspin(1,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
            runtime=float(centeringrevs)*float(((Nstepsperrev*multiplier)/freq)) #time for stepper to run at frequency to rotate desired revolutions
            time.sleep(runtime) #run stepper for runtime
            retry=0
        else:
            retry=retry+1
            
#level arm after centered
mc.armspin(0,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
runtime=float(.3711)*float(((Nstepsperrev*multiplier)/freq)) # rotate from centered to horizontal/ revolutions form model
print runtime
time.sleep(runtime)
mc.armspin(0,1,freq,m1,m2,100)
time.sleep(3)

#scanning routine

pi=math.pi #pi constant
zaxisrevs=0 # intialize zaxis revolution counter
armrevstep=.005 # number of revs between each measurement
zaxisrevstep=.005 # number revs to turn zaxis each step

freq=150
select.select((video,), (), (), 1)  
image_data1 = video.read_and_queue() #read image one time befor starting otherwise image is one step behind

# scan loop
loopend=0 #initialize looping variable
f = open('points2.xyz', 'a') #open point file for appending scan points xyz ascii data
while not loopend:
    mc.zaxis(0,1,freq,m1,m2,100) # lock z axis
    exit=0 
    armrevs=0 # intialize arm revolution counter
    while not exit:
        mc.armspin(0,1,freq,m1,m2,100) #hold arm
        select.select((video,), (), (), 1)
        image_data = video.read_and_queue()
        done = False # initialize loop variable
        i=size_x # initialize iterator
        firstpoint=0
        numpointsfound = 0
  
        while not done:
            i=i-1
            # read raw r,g,b
            #0,0 1,0.......799,0
            #.
            #
            #599,0 599,1.....599,799
            
            Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
            rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
            rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
            rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
            if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
                if firstpoint == 0: #find first pixle that is green
                    firstpoint =i
                numpointsfound += 1 # count green pixels found
            if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
                done=True
        if numpointsfound >1: # only calculate point coords if point is found
            centerpoint=(firstpoint+firstpoint+numpointsfound)/2
            sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera)
            distance=math.tan(angle_tilt_rad+sigma)*Spacing
            cosalpha=math.cos(armrevs*2*pi) # cosine
            sinalpha=math.sin(armrevs*2*pi) # sine
            costheta=math.cos(zaxisrevs*2*pi) # cosine zaxis rotation
            sintheta=math.sin(zaxisrevs*2*pi) # cosine zaxis rotation
             
            # pre rotation point on xz plane
            # distance to pivot/2, distance
            # rotate about y axis first y stays constant
            xprime=((Spacing/2)*cosalpha-distance*sinalpha)
            zprime=((Spacing/2)*sinalpha+distance*cosalpha)
            yprime=0
           
            #rotate about z axis second z stays constant
            x=-1*(xprime*costheta)
            y=(xprime*sintheta)
            z=(zprime)
            print>>f, str(x),str(y),str(z),'50','50','50', '\n'
        armrevs=armrevs+armrevstep #count up arm revolutions
        mc.armspin(0,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
        runtime=float(armrevstep)*float(((Nstepsperrev*multiplier)/freq))
        time.sleep(runtime-0.0012) #rotation time offest by program scan time until stop command is given
        if armrevs>=.995:
            exit=1 # end arm rotation after once around
    mc.armspin(0,1,freq,m1,m2,100) # lock arm
    zaxisrevs=zaxisrevs+zaxisrevstep #count up zaxis revolutions
    mc.zaxis(0,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
    runtime=float(zaxisrevstep)*float(((Nstepsperrev*multiplier)/freq))
    time.sleep(runtime)   #-0.0012)
    print zaxisrevs
    if zaxisrevs>=.5:
        loopend=1
f.close() #close xyz file
mc.cutpower()
video.close()
pause=input()

3d Scanner version 2.py

Python
2nd version of 3d scanner python script. less camera resolution faster scans but less accurate. fastest version of scanner, runs arm spin separate from data capture.
need to calibrate tilt angle, spacing, etc to get distances correct. need to change distance to center to get centering loop to work
# by chris martin 6/25/2020
# Uses bits and pieces from the internet
# Python Script is for Python 2.7.16
# test on beaglebone bla

from Stepper_Motor_Control import motorcontrol  #motor class 
from PIL import Image # not really needed, but does help finding laser dot when setting up pixel locations
import select # Not sure what features this library all has
import time # maybe better to use Gevent. eventually
import v4l2capture # for usb camera
import subprocess #allows command line to be used in the program
import math #math and trig functions

# usb camera code http://www.elpcctv.com/mi5100-5mp-usb-camera-module-usb20-aptina-125inch-color-cmos-sensor-100degree-lens-p-221.html

video = v4l2capture.Video_device("/dev/video0") # access usb camera # Open the video device.

subprocess.check_call("v4l2-ctl -c exposure_absolute=150",shell=True) # dim exposure to reduce false positives defualt for camera is 500 
subprocess.check_call("v4l2-ctl -c exposure_auto=1",shell=True) # dim exposure to reduce false positives

# Suggest an image size to the device. The device may choose and
# return another size if it doesn't support the suggested one.

Pixle_row_number=542 #grabbed from image  need to make calibration routine.
H_resolution = 1600  #camera horizontal resolution
V_resolution = 1200  #camera vertical resolution
H_max_resolution = 2592 # elp 5 megapixle camera
V_max_resolution = 1944 # elp 5 megapixle camera
                        # 2592x1944 @ 15fps MJPEG Fails 
                        # 2048x1536 @ 15fps MJPEG Works slower center is 690
                        # 1600x1200 @ 15fps MJPEG Works
                        # 1280x1024 @ 15fps MJPEG
                        # 1920x1080 @ 30fps MJPEG 
                        # 1280x720  @ 30fps MJPEG
                        # 800x600   @ 30fps MJPEG center is 265
                        # 640x480   @ 30fps MJPEG
size_base_pixel = 0.0022 #mm from specs
h_sensor = H_max_resolution*size_base_pixel   #sensor width
w_sensor = V_max_resolution*size_base_pixel   #sensor height
f_camera = 3 #mm focal length
angle_tilt_degrees=50.90 # 3d model is 50 degrees use this to tune distance measurement
angle_tilt_rad=math.radians(angle_tilt_degrees) # convert tilt angle to radians for trig functions
Spacing=320.0 #mm between laser and camera center to pivot axis intersection longer than camera focus 
centerdistancemeasured= 208.3  # measure this value using distance calibration program 208.5 for 2048x1536 210 for 800x600 209-210 for 1600x1200
size_x, size_y = video.set_format( H_resolution,V_resolution,0) #get actual camera set up resolution
H_pixle_width=h_sensor/size_x #lower resolutions group pixles one pixle
V_pixle_width=w_sensor/size_y #lower resolutions group pixles one pixle
#camera data set up

print "size:", size_x, size_y # display resolution
# Create a buffer to store image data in. This must be done before
# calling 'start' if v4l2capture is compiled with libv4l2. Otherwise
# raises IOError.
video.create_buffers(1)
# Start the device. This lights the LED if it's a camera that has one.
video.start()
# Send the buffer to the device.
video.queue_all_buffers()
# Wait a little. Some cameras take a few seconds to get bright enough.
time.sleep(.2)
print "camera set up"
# End Camera and scanner setup 


# create stepper motor control

mc=motorcontrol() # mc stepper motor 
freq=500 #pwm frequency

centeringrevs=.005
Nstepsperrev=200.0 # physical steps per rev 1.8 deg stepper motor tested at 200

m1=1
m2=1
        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
if m1==0 and m2 == 0:
    multipier=1
if m1==1 and m2 == 0:
    multipier=4  
if m1==0 and m2 == 1:
    multipier=16
if m1==1 and m2 == 1:
    multiplier=32    

mc.armspin(0,1,freq,m1,m2,100) # lock arm duty=100
mc.zaxis(0,1,freq,m1,m2,100) #lock zaxis duty=100

# Find center of scanner base does not work the best, but is somewhat repeatable, would be better to have encoder
# program looks for laser point, counts how many pixles wide it is and calcs the average (center)
# the distance is measured many time and compared with actual center distance to pivot to determine center of scanner
# after center is found the arm, is rotated to horizontal
centerfound = 0 #intialize center finding loop
retry=0 # each measurement is taken multiple times
maxretries=5 # each measurement is taken multiple times

while not centerfound: #Loop to find center of base
    mc.armspin(0,1,freq,m1,m2,100) # set arm pwm to 100 duty cycle locking it in place
    select.select((video,), (), (), 1)  #wait for camera to fill buffer
    image_data = video.read_and_queue()  #move camera buffer to program buffer
    done = False # initialize inner loop (look for laser points)
    i=size_x # initialize iterator
    firstpoint=0 #intialize variable 
    numpointsfound = 0 #initialize variable
  
    while not done:
        i=i-1
        # read raw r,g,b
        #0,0 1,0.......799,0
        #.
        #
        #599,0 599,1.....599,799
        Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
        rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
        rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
        rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
        if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
           if firstpoint == 0: #find first pixle that is green
               firstpoint =i
           numpointsfound += 1 # count green pixels found
        if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
            done=True
    centerpoint =(firstpoint+firstpoint+numpointsfound)/2 #average location of green pixles found 
    sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera) # angle between line from pixle average to focus and line from focus to center of sensor
    distance=math.tan(angle_tilt_rad+sigma)*Spacing # calulate distance from pivot plane to laser point
    distancetocenter=math.sqrt(distance*distance+((Spacing/2)*(Spacing/2))) # calulate distance from pivot to laser point
    print "distance to center :",distancetocenter
    if distancetocenter >=(centerdistancemeasured) and distancetocenter<(centerdistancemeasured+1): #look for center
        print "centered" 
        centerfound=1
    else:
        if retry >=maxretries: # rotate arm one step if not found
            mc.armspin(1,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
            runtime=float(centeringrevs)*float(((Nstepsperrev*multiplier)/freq)) #time for stepper to run at frequency to rotate desired revolutions
            time.sleep(runtime) #run stepper for runtime
            retry=0
        else:
            retry=retry+1
            
#level arm after centered
mc.armspin(0,1,freq,m1,m2,50) #armspin(self,direction,active,frequency,m1,m2):
runtime=float(.3711)*float(((Nstepsperrev*multiplier)/freq)) # rotate from centered to horizontal/ revolutions form model
print runtime
time.sleep(runtime)
mc.armspin(0,1,freq,m1,m2,100)
time.sleep(3)

#scanning routine

pi=math.pi #pi constant
zaxisrevs=0 # intialize zaxis revolution counter

zaxisrevstep=.001 # number revs to turn zaxis each step

freq=50
zfreq=100
select.select((video,), (), (), 1)  
image_data1 = video.read_and_queue() #read image one time befor starting otherwise image is one step behind
starttime=(time.time()+.052)
mc.armspin(0,1,freq,m1,m2,50) 

# scan loop
loopend=0 #initialize looping variable
f = open('pointsversion2.xyz', 'a') #open point file for appending scan points xyz ascii data
while not loopend:
    mc.zaxis(0,1,freq,m1,m2,100) # lock z axis
    exit=0 
    
    while not exit:
        
        timeimagecapture=time.time()
        select.select((video,), (), (), 1)
        image_data = video.read_and_queue()
        done = False # initialize loop variable
        i=size_x # initialize iterator
        firstpoint=0
        numpointsfound = 0
  
        while not done:
            i=i-1
            # read raw r,g,b
            #0,0 1,0.......799,0
            #.
            #
            #599,0 599,1.....599,799
            
            Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
            rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
            rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
            rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
            if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
                if firstpoint == 0: #find first pixle that is green
                    firstpoint =i
                numpointsfound += 1 # count green pixels found
            if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
                done=True
        if numpointsfound >1: # only calculate point coords if point is found
            runtime=timeimagecapture-starttime
            armrevs= runtime/(float(((Nstepsperrev*multiplier)/freq)))
            centerpoint=(firstpoint+firstpoint+numpointsfound)/2
            sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera)
            distance=math.tan(angle_tilt_rad+sigma)*Spacing
            cosalpha=math.cos(armrevs*2*pi) # cosine
            sinalpha=math.sin(armrevs*2*pi) # sine
            costheta=math.cos(zaxisrevs*2*pi) # cosine zaxis rotation
            sintheta=math.sin(zaxisrevs*2*pi) # cosine zaxis rotation
             
            # pre rotation point on xz plane
            # distance to pivot/2, distance
            # rotate about y axis first y stays constant
            xprime=((Spacing/2)*cosalpha-distance*sinalpha)
            zprime=((Spacing/2)*sinalpha+distance*cosalpha)
            yprime=0
           
            #rotate about z axis second z stays constant
            x=-1*(xprime*costheta)
            y=(xprime*sintheta)
            z=(zprime)
            print>>f, str(x),str(y),str(z),'50','50','50', '\n'
        
        
        print cosalpha
        if cosalpha>=0.9999:
            exit=1 # end arm rotation after once around
    
    zaxisrevs=zaxisrevs+zaxisrevstep #count up zaxis revolutions
    mc.zaxis(0,1,zfreq,m1,m2,50) 
    runtime=float(zaxisrevstep)*float(((Nstepsperrev*multiplier)/zfreq))
    time.sleep(runtime)   #-0.0012)
    print zaxisrevs
    if zaxisrevs>=.5:
        loopend=1
f.close() #close xyz file
mc.cutpower()
video.close()
pause=input()

3d Scanner version 3.py

Python
3rd version of 3d scanner python script. less camera resolution faster scans but less accurate. runs steppers a set number of pulses versus using timers.
need to calibrate tilt angle, spacing, etc to get distances correct. need to change distance to center to get centering loop to work
# by chris martin 6/25/2020
# Uses bits and pieces from the internet
# Python Script is for Python 2.7.16
# test on beaglebone bla

from Stepper_Motor_Control_2 import motorcontrol  #motor class 
from PIL import Image # not really needed, but does help finding laser dot when setting up pixel locations
import select # Not sure what features this library all has
import time # maybe better to use Gevent. eventually
import v4l2capture # for usb camera
import subprocess #allows command line to be used in the program
import math #math and trig functions

# usb camera code http://www.elpcctv.com/mi5100-5mp-usb-camera-module-usb20-aptina-125inch-color-cmos-sensor-100degree-lens-p-221.html

video = v4l2capture.Video_device("/dev/video0") # access usb camera # Open the video device.

subprocess.check_call("v4l2-ctl -c exposure_absolute=150",shell=True) # dim exposure to reduce false positives defualt for camera is 500 
subprocess.check_call("v4l2-ctl -c exposure_auto=1",shell=True) # dim exposure to reduce false positives

# Suggest an image size to the device. The device may choose and
# return another size if it doesn't support the suggested one.

Pixle_row_number=542 #grabbed from image  need to make calibration routine.
H_resolution = 1600  #camera horizontal resolution
V_resolution = 1200  #camera vertical resolution
H_max_resolution = 2592 # elp 5 megapixle camera
V_max_resolution = 1944 # elp 5 megapixle camera
                        # 2592x1944 @ 15fps MJPEG Fails 
                        # 2048x1536 @ 15fps MJPEG Works slower center is 690
                        # 1600x1200 @ 15fps MJPEG Works
                        # 1280x1024 @ 15fps MJPEG
                        # 1920x1080 @ 30fps MJPEG 
                        # 1280x720  @ 30fps MJPEG
                        # 800x600   @ 30fps MJPEG center is 265
                        # 640x480   @ 30fps MJPEG
size_base_pixel = 0.0022 #mm from specs
h_sensor = H_max_resolution*size_base_pixel   #sensor width
w_sensor = V_max_resolution*size_base_pixel   #sensor height
f_camera = 3 #mm focal length
angle_tilt_degrees=50.90 # 3d model is 50 degrees use this to tune distance measurement
angle_tilt_rad=math.radians(angle_tilt_degrees) # convert tilt angle to radians for trig functions
Spacing=320.0 #mm between laser and camera center to pivot axis intersection longer than camera focus 
centerdistancemeasured= 208.3  # measure this value using distance calibration program 208.5 for 2048x1536 210 for 800x600 209-210 for 1600x1200
size_x, size_y = video.set_format( H_resolution,V_resolution,0) #get actual camera set up resolution
H_pixle_width=h_sensor/size_x #lower resolutions group pixles one pixle
V_pixle_width=w_sensor/size_y #lower resolutions group pixles one pixle
#camera data set up

print "size:", size_x, size_y # display resolution
# Create a buffer to store image data in. This must be done before
# calling 'start' if v4l2capture is compiled with libv4l2. Otherwise
# raises IOError.
video.create_buffers(1)
# Start the device. This lights the LED if it's a camera that has one.
video.start()
# Send the buffer to the device.
video.queue_all_buffers()
# Wait a little. Some cameras take a few seconds to get bright enough.
time.sleep(.2)
print "camera set up"
# End Camera and scanner setup 


# create stepper motor control

mc=motorcontrol() # mc stepper motor 
freq=500 #pwm frequency

centeringrevs=.005
Nstepsperrev=200.0 # physical steps per rev 1.8 deg stepper motor tested at 200
centeringspeed=400
m1=1
m2=1

mc.armspin2(0,.001,centeringspeed,m1,m2) # lock arm duty=100
mc.zaxis2(0,.001,centeringspeed,m1,m2) #lock zaxis duty=100

# Find center of scanner base does not work the best, but is somewhat repeatable, would be better to have encoder
# program looks for laser point, counts how many pixles wide it is and calcs the average (center)
# the distance is measured many time and compared with actual center distance to pivot to determine center of scanner
# after center is found the arm, is rotated to horizontal
centerfound = 0 #intialize center finding loop
retry=0 # each measurement is taken multiple times
maxretries=5 # each measurement is taken multiple times

while not centerfound: #Loop to find center of base
    select.select((video,), (), (), 1)  #wait for camera to fill buffer
    image_data = video.read_and_queue()  #move camera buffer to program buffer
    done = False # initialize inner loop (look for laser points)
    i=size_x # initialize iterator
    firstpoint=0 #intialize variable 
    numpointsfound = 0 #initialize variable
  
    while not done:
        i=i-1
        # read raw r,g,b
        #0,0 1,0.......799,0
        #.
        #
        #599,0 599,1.....599,799
        Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
        rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
        rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
        rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
        if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
           if firstpoint == 0: #find first pixle that is green
               firstpoint =i
           numpointsfound += 1 # count green pixels found
        if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
            done=True
    centerpoint =(firstpoint+firstpoint+numpointsfound)/2 #average location of green pixles found 
    sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera) # angle between line from pixle average to focus and line from focus to center of sensor
    distance=math.tan(angle_tilt_rad+sigma)*Spacing # calulate distance from pivot plane to laser point
    distancetocenter=math.sqrt(distance*distance+((Spacing/2)*(Spacing/2))) # calulate distance from pivot to laser point
    print "distance to center :",distancetocenter
    if distancetocenter >=(centerdistancemeasured) and distancetocenter<(centerdistancemeasured+1): #look for center
        print "centered" 
        centerfound=1
    else:
        if retry >=maxretries: # rotate arm one step if not found
            mc.armspin2(1,centeringrevs,centeringspeed,m1,m2) #armspin(self,direction,active,frequency,m1,m2):
            retry=0
        else:
            retry=retry+1
            
#level arm after centered
mc.armspin2(0,.3711,centeringspeed*2,m1,m2) #armspin(self,direction,active,frequency,m1,m2):
time.sleep(3)

#scanning routine

pi=math.pi #pi constant
zaxisrevs=0 # intialize zaxis revolution counter

scanningspeed=350
rotatezspeed=200
select.select((video,), (), (), 1)  
image_data1 = video.read_and_queue() #read image one time befor starting otherwise image is one step behind
armrevstep=.005
zaxisrevstep=.0025 # number revs to turn zaxis each step
# scan loop
loopend=0 #initialize looping variable
f = open('points2.xyz', 'a') #open point file for appending scan points xyz ascii data
while not loopend:
    exit=0 
    armrevs=0 # intialize arm revolution counter
    while not exit:
        select.select((video,), (), (), 1)
        image_data = video.read_and_queue()
        done = False # initialize loop variable
        i=size_x # initialize iterator
        firstpoint=0
        numpointsfound = 0
  
        while not done:
            i=i-1
            # read raw r,g,b
            #0,0 1,0.......799,0
            #.
            #
            #599,0 599,1.....599,799
            
            Offset=Pixle_row_number*H_resolution #image buffer is series of 3 color tupples offest is hwo far into the buffer the start of the center row or column is.
            rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1])) #read green 
            rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3])) #read red
            rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2])) #read blue
            if ((rawpointcolorg>245) and (rawpointcolorr<245)): # look for green laser, filter out some bright light, by using red value
                if firstpoint == 0: #find first pixle that is green
                    firstpoint =i
                numpointsfound += 1 # count green pixels found
            if i==0 or (i < (firstpoint-30)): # if no green pixle found or 30 pixles past the first one found are checked then stop looking
                done=True
        if numpointsfound >1: # only calculate point coords if point is found
            centerpoint=(firstpoint+firstpoint+numpointsfound)/2
            sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera)
            distance=math.tan(angle_tilt_rad+sigma)*Spacing
            cosalpha=math.cos(armrevs*2*pi) # cosine
            sinalpha=math.sin(armrevs*2*pi) # sine
            costheta=math.cos(zaxisrevs*2*pi) # cosine zaxis rotation
            sintheta=math.sin(zaxisrevs*2*pi) # cosine zaxis rotation
             
            # pre rotation point on xz plane
            # distance to pivot/2, distance
            # rotate about y axis first y stays constant
            xprime=((Spacing/2)*cosalpha-distance*sinalpha)
            zprime=((Spacing/2)*sinalpha+distance*cosalpha)
            yprime=0
           
            #rotate about z axis second z stays constant
            x=-1*(xprime*costheta)
            y=(xprime*sintheta)
            z=(zprime)
            print>>f, str(x),str(y),str(z),'50','50','50', '\n'
        armrevs=armrevs+armrevstep #count up arm revolutions
        mc.armspin2(0,armrevstep,scanningspeed,m1,m2) #armspin(self,direction,active,frequency,m1,m2):
        if armrevs>=1:
            exit=1 # end arm rotation after once around
            print armrevs
    zaxisrevs=zaxisrevs+zaxisrevstep #count up zaxis revolutions
    mc.zaxis2(0,zaxisrevstep,rotatezspeed,m1,m2) #armspin(self,direction,active,frequency,m1,m2):
    print zaxisrevs
    if zaxisrevs>=.5:
        loopend=1

f.close() #close xyz file
mc.cutpower()
video.close()
pause=input()

calibration help tool.py

Python
helps to set up scan script by finding centering distance and other tasks. calibrate tilt angle with tape measure.
#!/usr/bin/python
# https://github.com/gebart/python-v4l2capture/blob/master/capture_picture_delayed.py
# python-v4l2capture
#
# This file is an example on how to capture a picture with
# python-v4l2capture. It waits between starting the video device and
# capturing the picture, to get a good picture from cameras that
# require a delay to get enough brightness. It does not work with some
# devices that require starting to capture pictures immediatly when
# the device is started.
#
# 2009, 2010 Fredrik Portstrom
#
# I, the copyright holder of this file, hereby release it into the
# public domain. This applies worldwide. In case this is not legally
# possible: I grant anyone the right to use this work for any
# purpose, without any conditions, unless such conditions are
# required by law.

from PIL import Image
import select
import time
import v4l2capture
import subprocess
import math


# Open the video device.
# too list cameras v4l2-ctl --list-devices
print time.clock()

video = v4l2capture.Video_device("/dev/video0")

subprocess.check_call("v4l2-ctl -c exposure_absolute=150",shell=True)
subprocess.check_call("v4l2-ctl -c exposure_auto=1",shell=True)
# subprocess.check_call("v4l2-ctl -D -l",shell=True)
# Suggest an image size to the device. The device may choose and
# return another size if it doesn't support the suggested one.
Pixle_row_number=542 #grabbed from image  need to make calibration routine.
H_resolution = 1600
V_resolution = 1200
H_max_resolution = 2592 # elp 5 megapixle camera
V_max_resolution = 1944 # elp 5 megapixle camera
size_base_pixel = 0.0022 #mm
h_sensor = H_max_resolution*size_base_pixel   #sensor width
w_sensor = V_max_resolution*size_base_pixel   #sensor height
f_camera = 3 #mm focal length
angle_tilt_degrees=50.675
offset_to_pivot=00 #mm
angle_tilt_rad=math.radians(angle_tilt_degrees)
print angle_tilt_rad, " tilt angle"
Spacing=320.0 #mm between laser and camera focus
size_x, size_y = video.set_format( H_resolution,V_resolution,0)

H_pixle_width=h_sensor/size_x #lower resolutions group pixles one pixle
V_pixle_width=w_sensor/size_y #lower resolutions group pixles one pixle



print "size:", size_x, size_y
print "1"
# Create a buffer to store image data in. This must be done before
# calling 'start' if v4l2capture is compiled with libv4l2. Otherwise
# raises IOError.
video.create_buffers(1)
print "2"
# Start the device. This lights the LED if it's a camera that has one.
video.start()
# Send the buffer to the device.
print "3"
video.queue_all_buffers()
# Wait a little. Some cameras take a few seconds to get bright enough.
# time.sleep(2)
print "4"


# Wait for the device to fill the buffer.

snapshot=0
averager=0


print time.clock()
while True:
    
    select.select((video,), (), (), 5)
    image_data = video.read_and_queue()
    snapshot=snapshot+1
    done = False
    i=size_x # initialize iterator
    firstpoint=0
    numpointsfound = 0
  
    while not done:
        i=i-1
        # read raw r,g,b
        #0,0 1,0.......799,0
        #.
        #
        #599,0 599,1.....599,799
        Offset=Pixle_row_number*H_resolution
        rawpointcolorg=ord(bytearray(image_data[(i+Offset)*3+1]))
        rawpointcolorr=ord(bytearray(image_data[(i+Offset)*3]))
        rawpointcolorb=ord(bytearray(image_data[(i+Offset)*3+2]))
        
        if ((rawpointcolorg>240) and (rawpointcolorr<245)): 
           print i,Pixle_row_number, ":", rawpointcolorr,rawpointcolorg, rawpointcolorb
           if firstpoint == 0:
               firstpoint =i
           numpointsfound += 1
           
        if i==0 or (i < (firstpoint-20)):
            done=True
    centerpoint =(firstpoint+firstpoint+numpointsfound)/2
    sigma=math.atan((H_pixle_width*((centerpoint)-(size_x/2)))/f_camera)
    distance=math.tan(angle_tilt_rad+sigma)*Spacing+offset_to_pivot
    distancetocenter=math.sqrt(distance*distance+((Spacing/2)*(Spacing/2))) # calulate distance from pivot to laser point
    print "angle:", sigma,"distance :",distance/25.4, distancetocenter    
    averager=averager+distance
    if snapshot >=10:
        print (averager/(snapshot*25.4)) 
        print snapshot 
        print time.clock()
        break
image = Image.frombuffer("RGB", (size_x, size_y), image_data,'raw',"RGB",0,1)
video.close()
f = open('my_file', 'w+b')
f.write(image_data)
f.close()

image.save("image.jpg")
print "number of points found ", numpointsfound
print "Saved image.jpg (Size: " + str(size_x) + " x " + str(size_y) + ")"
print "done"

#subprocess.check_call("v4l2-ctl -D -l",shell=True)

Stepper_Motor_Control.py

Python
1st version of stepper class
#motor control
import Adafruit_BBIO.GPIO as GPIO
import Adafruit_BBIO.PWM as PWM
import time
import subprocess

# NOTES

# variable to set frequency and pulse width for testing
        #freq = 500 #Hz
        #duty = 50 #%
        #PWM.start("P8_34",duty,freq,0)  #PWM1A pin P8-36
        #PWM.start("P9_31",duty,freq,0)  #PWM1A pin P8-36
        # need to set pin to pwm # config-pin -a p9.31 pwm 
        #PWM.start("P8_45",duty,freq,0)  #PWM2A pin P8-45
        # need to set pin to pwm # config-pin -a p8.45 pwm 


class motorcontrol(object):
    #globals for testing
    
    # intitialize
    def __init__(self):
        
        # line runs bash command to config pin, if the cape manager is not loaded it will be triggered to load doesnt matter what pin is set
        #subprocess.check_call("config-pin -a p9.14 pwm",shell=True)
        
        # Stepper Motor Control 1 & 2 (Camera arm spin and z axis)
        # set up pins to run stepper motor pulse & direction
        
        #armspin
        
        GPIO.setup("P9_11", GPIO.OUT) # pinarmspinreset
        GPIO.setup("P9_13", GPIO.OUT) # pinarmspinM1
        GPIO.setup("P9_15", GPIO.OUT) # pinarmspinM2
        GPIO.setup("P9_17", GPIO.OUT) # pinarmspindir
        
        #zxis
        
        GPIO.setup("P9_23", GPIO.OUT) # pinzaxisreset
        GPIO.setup("P9_25", GPIO.OUT) # pinzaxisM1
        GPIO.setup("P9_27", GPIO.OUT) # pinzaxisM2
        GPIO.setup("P9_24", GPIO.OUT) # pinzaxisdir 
        
        print "initilized"
    
        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
    
    # destructor
    def __del__(self):
        
        #armspin
        PWM.stop("P9_21") #pin P9-21 PWM
        GPIO.output("P9_11", 0) # pinarmspinreset
        GPIO.output("P9_13", 0) # pinarmspinM1
        GPIO.output("P9_15", 0) # pinarmspinM2
        GPIO.output("P9_17", 0) # pinarmspindir
       
        #zxis
        PWM.stop("P9_14") #pin P9-31 PWM
        GPIO.output("P9_23", 0) # pinzaxisreset
        GPIO.output("P9_25", 0) # pinzaxisM1
        GPIO.output("P9_27", 0) # pinzaxisM2
        GPIO.output("P9_24", 0) # pinzaxisdir
        
        PWM.cleanup()
        print 'end'

    def cutpower(self):
        #armspin
        PWM.stop("P9_21") #pin P9-21 PWM
        GPIO.output("P9_11", 0) # pinarmspinreset
        GPIO.output("P9_13", 0) # pinarmspinM1
        GPIO.output("P9_15", 0) # pinarmspinM2
        GPIO.output("P9_17", 0) # pinarmspindir
       
        #zxis
        PWM.stop("P9_14") #pin P9-31 PWM
        GPIO.output("P9_23", 0) # pinzaxisreset
        GPIO.output("P9_25", 0) # pinzaxisM1
        GPIO.output("P9_27", 0) # pinzaxisM2
        GPIO.output("P9_24", 0) # pinzaxisdir
        print 'end'
        PWM.cleanup()

    def armspin(self,direction,active,frequency,m1,m2, duty):
        #freq = float(frequency) #Hz 20000 #Hz
        
        PWM.start("P9_21",duty,frequency,0)  
        
        if active==1:
           
            GPIO.output("P9_11", 1) # pinarmspinreset
            GPIO.output("P9_13", m1) # pinarmspinM1
            GPIO.output("P9_15", m2) # pinarmspinM2
            GPIO.output("P9_17", direction) # pinarmspindir
            
            
        if active==0:
            PWM.stop("P9_21")
            GPIO.output("P9_11", 1) # pinarmspinreset
            GPIO.output("P9_13", 0) # pinarmspinM1
            GPIO.output("P9_15", 0) # pinarmspinM2
            GPIO.output("P9_17", 0) # pinarmspindir
            PWM.cleanup()
            
            
    def zaxis(self,direction,active,frequency,m1,m2,duty):
        #freq = frequency #Hz 20000 #Hz
        
        PWM.start('P9_14',duty,frequency,0)  
        
        if active==1:
            GPIO.output("P9_23", 1) # pinzaxisreset
            GPIO.output("P9_25", m1) # pinzaxisM1
            GPIO.output("P9_27", m2) # pinzaxisM2
            GPIO.output("P9_24", direction) # pinzaxisdir
            
        if active==0:
            PWM.stop("P9_14")
            GPIO.output("P9_23", 1) # pinzaxisreset
            GPIO.output("P9_25", 0) # pinzaxisM1
            GPIO.output("P9_27", 0) # pinzaxisM2
            GPIO.output("P9_24", 0) # pinzaxisdir
            PWM.cleanup()
            
   

Stepper_Motor_Control_2.py

Python
2nd version of stepper class
#motor control
import Adafruit_BBIO.GPIO as GPIO
import time
import subprocess

# NOTES



class motorcontrol(object):
    #globals for testing
    
    # intitialize
    def __init__(self):
        
        # line runs bash command to config pin, 
        subprocess.check_call("config-pin p9.14 gpio",shell=True)
        subprocess.check_call("config-pin p9.21 gpio",shell=True)
        # Stepper Motor Control 1 & 2 (Camera arm spin and z axis)
        # set up pins to run stepper motor pulse & direction
        
        #armspin
        
        GPIO.setup("P9_11", GPIO.OUT) # pinarmspinreset
        GPIO.setup("P9_13", GPIO.OUT) # pinarmspinM1
        GPIO.setup("P9_15", GPIO.OUT) # pinarmspinM2
        GPIO.setup("P9_17", GPIO.OUT) # pinarmspindir
        GPIO.setup("P9_21", GPIO.OUT) # need to configure the pin as gpio vs pwm
        #zxis
        
        GPIO.setup("P9_23", GPIO.OUT) # pinzaxisreset
        GPIO.setup("P9_25", GPIO.OUT) # pinzaxisM1
        GPIO.setup("P9_27", GPIO.OUT) # pinzaxisM2
        GPIO.setup("P9_24", GPIO.OUT) # pinzaxisdir 
        GPIO.setup("P9_14", GPIO.OUT) # need to configure the pin as gpio vs pwm
        print "initilized"
    
        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
    
    # destructor
    def __del__(self):
        
        #armspin
        
        GPIO.output("P9_11", 0) # pinarmspinreset
        GPIO.output("P9_13", 0) # pinarmspinM1
        GPIO.output("P9_15", 0) # pinarmspinM2
        GPIO.output("P9_17", 0) # pinarmspindir
        GPIO.output("P9_21", 0) # pulses
        #zxis
        GPIO.output("P9_23", 0) # pinzaxisreset
        GPIO.output("P9_25", 0) # pinzaxisM1
        GPIO.output("P9_27", 0) # pinzaxisM2
        GPIO.output("P9_24", 0) # pinzaxisdir
        GPIO.output("P9_14", 0) # pulses
        print 'end'

    def cutpower(self):
        #armspin
        
        GPIO.output("P9_11", 0) # pinarmspinreset
        GPIO.output("P9_13", 0) # pinarmspinM1
        GPIO.output("P9_15", 0) # pinarmspinM2
        GPIO.output("P9_17", 0) # pinarmspindir
        GPIO.output("P9_21", 0) # pulses
        
        #zxis
        GPIO.output("P9_23", 0) # pinzaxisreset
        GPIO.output("P9_25", 0) # pinzaxisM1
        GPIO.output("P9_27", 0) # pinzaxisM2
        GPIO.output("P9_24", 0) # pinzaxisdir
        GPIO.output("P9_14", 0) # pulses
        print 'end'
        
    def zaxis2(self,direction,revs,speed,m1,m2):
        
        Nstepsperrev=200.0 # physical steps per rev 1.8 deg stepper motor tested at 200

        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
        if m1==0 and m2 == 0:
            multiplier=1
        if m1==1 and m2 == 0:
            multiplier=4  
        if m1==0 and m2 == 1:
            multiplier=16
        if m1==1 and m2 == 1:
            multiplier=32    
        
        GPIO.output("P9_23", 1) # pinzaxisreset
        GPIO.output("P9_25", m1) # pinzaxisM1
        GPIO.output("P9_27", m2) # pinzaxisM2
        GPIO.output("P9_24", direction) # pinzaxisdir
        
        done=0
        pulses=float(0)
        sleeptime=1/float(speed)
        while not done:
            GPIO.output("P9_14",1) #need to reconfigure pin or move to different pin
            time.sleep(sleeptime)
            GPIO.output("P9_14",0)
            time.sleep(sleeptime)
            pulses=pulses+float(1/(Nstepsperrev*multiplier))
            
            if pulses >= (revs):
                done =1
                    
        
            
    def armspin2(self,direction,revs,speed,m1,m2):
        
        Nstepsperrev=200.0 # physical steps per rev 1.8 deg stepper motor tested at 200

        # M1=0, M2=0 full step
        # M1=1, M2=0 1/4 step
        # M1=0, M2=1 1/16 step
        # M1=1, M2=1 1/32 step
        if m1==0 and m2 == 0:
            multiplier=1
        if m1==1 and m2 == 0:
            multiplier=4  
        if m1==0 and m2 == 1:
            multiplier=16
        if m1==1 and m2 == 1:
            multiplier=32    
        
        GPIO.output("P9_11", 1) # pinarmspinreset
        GPIO.output("P9_13", m1) # pinarmspinM1
        GPIO.output("P9_15", m2) # pinarmspinM2
        GPIO.output("P9_17", direction) # pinarmspindir
        done=0
        pulses=float(0)
        sleeptime=1/float(speed)
        while not done:
            GPIO.output("P9_21",1) #need to reconfigure pin or move to different pin
            time.sleep(sleeptime)
            GPIO.output("P9_21",0)
            time.sleep(sleeptime)
            pulses=pulses+float(1/(Nstepsperrev*multiplier))
            
            if pulses >= (revs):
                done =1
        
            
            
        

time test.py

Python
more tools
import time # maybe better to use Gevent. eventually
from Stepper_Motor_Control_2 import motorcontrol  #motor class 

starttime=time.time()
print starttime
time.sleep(0)
endtime=time.time()
print endtime
print endtime-starttime

mc=motorcontrol()
m1=1
m2=1
print "set up"
done=0
while not done:
    print "enter revs:",
    revs=input()
    print int(revs)
    print "enter speed",
    speed=input()
    if revs <>0:
        mc.armspin2(0,revs,speed,m1,m2) #armspin(self,direction,active,frequency,m1,m2):
        
    else:
        done=1
        



print "done"

Credits

chris m
4 projects • 7 followers
Bored Mechanical Engineer

Comments