A camera is just an action which returns an image:
Type Camera = IO ImageA camera combinator is a function which typically takes one or more cameras and other arguments and produces a new camera. For instance, we can fuse the images obtained by two cameras into a "panoramic" view:
panoramic :: Camera -> Camera -> IO CameraThen any program using this camera automagically receives frames like this:
(Currently the required synthetic rotations must be adjusted manually, but we are working on an automatic method. And, of course, the cameras must be very close to each other for this to work.)
We can now create a panoramic view by joining an arbitrary number of cameras by something like this:
pano <- foldM panoramic (head cams) (tail cams)
(I will try to prepare a demo with 3 or 4 cameras...)
The real power of camera combinators in Haskell is the fact that we can work with the infinite lazy sequence of the images captured by the camera. We have defined a "virtualCamera" function which adapts ordinary list functions to work with the IO list of images. Using it we can define, for example, a camera which "intercalates" average frames:
interpolate = virtualCamera (return . inter)We can also compute a weighted average of the sequence:
where inter (a:b:rest) = a : x : inter (b:rest)
where x = 0.5 .* a |+| 0.5 .* b
drift alpha = virtualCamera (return . drifter)
where drifter (a:b:rest) = a : drifter (x:rest)
where x = alpha .* a |+| (1-alpha) .* b
For instance, the following effect:
can be obtained by the following composition of combinators:
cam <- getCam 0 sizeThe "worker" function receives just a simple IO ImageFloat:
>>= monitorizeIn "original" (Size 150 200) id
>>= asFloat
>>= drift alpha
>>= interpolate
worker cam win = doBut each call automatically performs all the computations defined above.
inWindow win $ do
cam >>= drawImage
We have defined camera combinators to filter frames very different from the previous ones (movement detectors), detectors of "static" frames, feature extractors, etc. Using this technique typical acquisition and preprocessing tasks can be easily uncoupled from the "consumer" applications.