Let’s do something fun! In this post we’ll take a photo using your webcam in Ruby.
NOTE: This only works on OS X
I haven’t tried making it work on other operating systems. Not that I don’t like other operating systems, I just haven’t made it work. :-)
Installing the Gem
We’ll use the
av_capture gem. It wraps the AVCapture framework on OS X. To
install it, just do:
$ gem install av_capture
Using the Gem
I’ll paste the code here first, then explain it:
require 'av_capture' # Create a recording session session = AVCapture::Session.new # Find the first video capable device dev = AVCapture.devices.find(&:video?) # Output the camera's name $stderr.puts dev.name # Connect the camera to the recording session session.run_with(dev) do |connection| # Capture an image and write it to $stdout $stdout.write connection.capture end
First the program creates a new capture session. OS X can capture from many multimedia devices, and we hook them together through a session. Next, it grabs the first device attached to the machine that has video capability. If your machine has multiple cameras, you may want to adjust this code. After it outputs the name of the camera, we tell the session to start and use that device.
The camera can only be used while the session is open, inside the block provided
run_with. Inside the block, we ask the connection to capture an image,
then write the image to
Running the code
I’ve saved the program in a file called
thing.rb. If you run the program like
$ ruby thing.rb | open -f -a /Applications/Preview.app
it should open Preview.app with an image captured from the camera.
Taking Photos Interactively
Let’s make this program a little more interactive:
require 'av_capture' require 'io/console' session = AVCapture::Session.new dev = AVCapture.devices.find(&:video?) session.run_with(dev) do |connection| loop do case $stdin.getch when 'q' then break # quit when you hit 'q' else IO.popen("open -g -f -a /Applications/Preview.app", 'w') do |f| f.write connection.capture end end end end
This program will just sit there until you press a button. Press ‘q’ to quit,
any other button to take a photo and display it in
io/console lets us read one character from
$stdin as soon as possible, and
the call to
IO.popen lets us write the data to
A Photo Server using DRb
It takes a little time for the camera to turn on before the program can take a photo. This causes a little lag time when we want to take a photo. In the spirit of over-engineering things, lets create a photo server using DRb. The server will keep the camera on and ready to take photos. The client will ask the server for photos.
Here is our server code:
require 'av_capture' require 'drb' class PhotoServer attr_reader :photo_request, :photo_response def initialize @photo_request = Queue.new @photo_response = Queue.new @mutex = Mutex.new end def take_photo @mutex.synchronize do photo_request << "x" photo_response.pop end end end server = PhotoServer.new Thread.new do session = AVCapture::Session.new dev = AVCapture.devices.find(&:video?) session.run_with(dev) do |connection| while server.photo_request.pop server.photo_response.push connection.capture end end end URI = "druby://localhost:8787" DRb.start_service URI, server DRb.thread.join
PhotoServer object has a request queue and a response queue. When a
client asks to take a photo by calling the
take_photo method, it writes a
request to the queue, then waits for a photo to be pushed on to the response
The AVCapture session’s run block waits for a request to appear on the
photo_request queue. When it gets a request on the queue, it takes a photo
and writes the photo to the response queue.
At the bottom of the file, we connect the
PhotoServer object to DRb on port
8787, and join the DRb server thread.
Here is our client code:
require 'drb' SERVER_URI = "druby://localhost:8787" photoserver = DRbObject.new_with_uri SERVER_URI print photoserver.take_photo
The client code connects to the DRb server on port 8787, requests a photo, then
writes the photo to
Running the code
In one terminal, run the server code like this:
$ ruby server.rb
Then in another terminal, run the client code like this:
$ ruby client.rb | open -f -a /Applications/Preview.app
You should have a photo show up in
Preview.app. You can kill the server
program by doing Ctrl-C.
Just for fun, let’s compare the speed of the first program to the speed of the
client program just using
time. Here is the first program:
$ time ruby thing.rb > /dev/null FaceTime HD Camera (Built-in) real 0m3.217s user 0m0.151s sys 0m0.069s
Here is the client program:
$ time ruby client.rb > /dev/null real 0m0.183s user 0m0.070s sys 0m0.038s
The first program takes about 3 seconds to take a photo where the second “client” program only takes 200ms or so. The reason the second program is much faster is because the server keeps the camera “hot”. Most of our time is spent getting the camera ready rather than taking photos.
Here are some weird photos that I made while I was writing this:
Happy Wednesday! <3<3<3<3