Webcam photos with Ruby
Mar 26, 2014 @ 1:47 pmLet’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
to run_with
. Inside the block, we ask the connection to capture an image,
then write the image to $stdout
.
Running the code
I’ve saved the program in a file called thing.rb
. If you run the program like
this:
$ 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 Preview.app
. Requiring
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 Preview.app
.
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.
Server code
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
The 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
queue.
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.
Client Code
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 $stdout
.
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.
Speed comparison
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.
Weird photos
Here are some weird photos that I made while I was writing this:
Happy Wednesday! <3<3<3<3