blag/posts/2016-10-12-pi-pan-tilt-3.md

200 lines
9.7 KiB
Markdown

---
title: Pi pan-tilt for huge images, part 3: ArduCam & raw images
author: Chris Hodapp
date: October 12, 2016
tags: photography, electronics, raspberrypi
---
This is the third part in this series, continuing on from
[part 1][part1] and [part 2][]. The last post was about integrating
the hardware with Hugin and PanoTools. This one is similarly
technical and without as many pretty pictures, so be forewarned.
Thus far (aside from my first stitched image) I've been using a raw
workflow where possible. That is, all images arrive from the camera
in a lossless format, and every intermediate step works in a lossless
format. To list out some typical steps in this:
- Acquire raw images from camera with [raspistill][].
- Convert these to (lossless) TIFFs with [dcraw][].
- Process these into a composite image with [Hugin][] & [PanoTools][],
producing another lossless TIFF file (for low dynamic range) or
[OpenEXR][] file (for [high dynamic range][hdr]).
- Import into something like [darktable][] for postprocessing.
I deal mostly with the first two here.
# Acquiring Images
I may have mentioned in the first post that I'm using
[ArduCam's Raspberry Pi camera][]. This board uses a 5-megapixel
[OmniVision OV5647][ov5647]. (I believe they have
[another][arducam_omx219] that uses the 8-megapixel Sony OMX 219, but
I haven't gotten my hands on one yet.)
If you are expecting the quality of sensor even an old DSLR camera
provides, this board's tiny, noisy sensor will probably disappoint
you. However, if you are accustomed to basically every other camera
that is within double the price and interfaces directly with a
computer of some kind (USB webcams and the like), I think you'll find
it quite impressive:
- It has versions in three lens mounts: CS, C, and M12. CS-mount and
C-mount lenses are plentiful from their existing use in security
cameras, generally inexpensive, and generally good enough quality
(and for a bit extra, ones are available with
electrically-controllable apertures and focus). M12 lenses (or
"board lenses") are... plentiful and inexpensive, at least. I'll
probably go into more detail on optics in a later post.
- 10-bit raw Bayer data straight off the sensor is available (see
[raspistill][] and its `--raw` option, or how
[picamera][picamera-raw] does it). Thus, we can bypass all of the
automatic brightness, sharpness, saturation, contrast, and
whitebalance correction which are great for snapshots and video, but
really annoying for composite images.
- Likewise via [raspistill][], we may directly set the ISO speed and
the shutter time in microseconds, bypassing all automatic exposure
control.
- It has a variety of features pertaining to video, none of which I
care about for this application. Go look in [picamera][] for the
details.
I'm mostly using the CS-mount version, which came with a lens that is
surprisingly sharp. If anyone can tell me how to do better for $30
(perhaps with those GoPro knockoffs that are emerging?), please tell
me.
Reading raw images from the Raspberry Pi cameras is a little more
convoluted, and I suspect that this is just how the CSI-2 pathway for
imaging works on the Raspberry Pi. In short: It produces a JPEG file
which contains a normal, lossy image, followed by a binary dump of the
raw sensor data, not as metadata, not as JPEG data, just... dumped
after the JPEG data. *(Where I refer to "JPEG image" here, I'm
referring to actual JPEG-encoded image data, not the binary dump stuck
inside something that is coincidentally a JPEG file.)*
Most of my image captures were with something like:
raspistill --raw -t 1 -w 640 -h 480 -ss 1000 -ISO 100 -o filename.jpg
That `-t 1` is to remove the standard 5-second timeout; I'm not sure
if I can take it lower. `-w 640 -h 480 -q 75` applies to the JPEG
image, while the raw data with `--raw` is always full-resolution; I'm
saving only a much-reduced JPEG as a thumbnail of the raw data, rather
than wasting the disk space and I/O on larger JPEG data than I'll use.
`-ss 1000` is for a 1000 microsecond exposure (thus 1 millisecond),
and `-ISO 100` is for ISO 100 speed (the lowest this sensor will do).
Note that we may also remove the `-ss` option and instead `-set` to
get lines like:
mmal: Exposure now 10970, analog gain 256/256, digital gain 256/256
mmal: AWB R=330/256, B=337/256
That 10970 is the shutter speed, again in microseconds, according to
the camera's metering. Analog and digital gain relate to ISO, but
only somewhat indirectly; setting ISO will result in changes to both,
and from what I've read, they both equal 1 if the ISO speed is 100.
I just switched my image captures to use [picamera][] rather than
`raspistill`. They both are fairly thin wrappers on top of the
hardware; the only real difference is that picamera exposes things via
a Python API rather than a commandline tool.
# Converting Raw Images
People have already put considerable work into converting these rather
strange raw image files into something more sane (as the Raspberry Pi
forums document [here][forum1] and [here][forum2]) - like the
[color tests][beale] by John Beale, and 6by9's patches to dcraw, some
of which have made it into Dave Coffin's official [dcraw][].
I've had to use 6by9's version of dcraw, which is at
<https://github.com/6by9/RPiTest/tree/master/dcraw>. As I understand
it, he's trying to get the rest of this included into official dcraw.
On an older-revision ArduCam board, I ran into problems getting 6by9's
dcraw to read the resultant raw images, which I fixed with a
[trivial patch][dcraw-pr]. However, that board had other problems, so
I'm no longer using it. (TODO: Explain those problems.)
My conversion step is something like:
dcraw -T -W *.jpg
`-T` writes a TIFF and passes through metadata `-W` tells dcraw to
leave the brightness alone; I found out the hard way that leaving this
out would lead to some images with mangled exposures. From here,
dcraw produces a `.tiff` for each `.jpg`. We can, if we wish, use all
of that 10-bit range by using `-6` to make a 16-bit TIFF rather than
an 8-bit one. In my own tests, though, it makes no difference
whatsoever because of the sensor's noisiness.
We can also rotate the image at this step, but I prefer to instead add
this as an initial roll value of -90, 90, or 180 degrees when creating
the PTO file. This keeps the lens parameters correct if, for
instance, we already have computed a distortion model of a lens.
To give an example of the little bit of extra headroom that raw images
provide, I took 9 example shots of the same scene, ranging from about
-1.0 underexposed down to -9.0 underexposed. The first grid is the
full-resolution JPEG image of these shots, normalized - in effect,
trying to re-expose them properly:
[![](../images/2016-10-12-pi-pan-tilt-3/tile_jpg.jpg){width=100%}](../images/2016-10-12-pi-pan-tilt-3/tile_jpg.jpg)
The below contains the raw sensor data, turned to 8-bit TIFF and then
again normalized. It's going to look different than the JPEG due to
the lack of whitebalance adjustment, denoising, brightness, contrast,
and so on.
[![](../images/2016-10-12-pi-pan-tilt-3/tile_8bit.jpg){width=100%}](../images/2016-10-12-pi-pan-tilt-3/tile_8bit.jpg)
These were done with 16-bit TIFFs rather than 8-bit ones:
[![](../images/2016-10-12-pi-pan-tilt-3/tile_16bit.jpg){width=100%}](../images/2016-10-12-pi-pan-tilt-3/tile_16bit.jpg)
In theory, the 16-bit ones should be retaining two extra bits of data
from the 10-bit sensor data, and thus two extra stops of dynamic
range, that the 8-bit image cannot keep. I can't see the slightest
difference myself. Perhaps those two bits are just well below the
noise floor; perhaps if I used a brighter scene, it would be more
apparent.
Regardless, starting from raw sensor data rather than the JPEG image
gets some additional dynamic range. That's hardly surprising - JPEG
isn't really known for its faithful reproduction of darker parts of an
image. Regardless, bracketing exposures and merging them later (as
Hugin will do) is probably a better idea than pretending the sensor is
going to give 10 bits of resolution.
Here's another comparison, this time a 1:1 crop from the center of an
image (shot with [this lens][12-40mm], whose Amazon price mysteriously
is now $146 instead of the $23 I actually paid). Click for a lossless
PNG view, as JPEG might eat some of the finer details.
[![JPEG & raw comparison](../assets_external/2016-10-12-pi-pan-tilt-3/leaves_test_preview.jpg){width=100%}](../assets_external/2016-10-12-pi-pan-tilt-3/leaves_test.png)
I'll cover the remaining two steps I noted - Hugin & PanoTools
stitching, and postprocessing - in the next post.
[part1]: ./2016-09-25-pi-pan-tilt-1.html
[part2]: ./2016-10-04-pi-pan-tilt-2.html
[raspistill]: https://www.raspberrypi.org/documentation/raspbian/applications/camera.md
[dcraw]: https://www.cybercom.net/~dcoffin/dcraw/
[Hugin]: http://wiki.panotools.org/Hugin
[PanoTools]: http://wiki.panotools.org/Main_Page
[OpenEXR]: http://www.openexr.com/
[hdr]: https://en.wikipedia.org/wiki/High-dynamic-range_imaging
[darktable]: http://www.darktable.org/
[ArduCam]: http://www.arducam.com/camera-modules/raspberrypi-camera/
[ov5647]: http://www.ovt.com/uploads/parts/OV5647.pdf
[arducam_omx219]: http://www.arducam.com/8mp-sony-imx219-camera-raspberry-pi/
[beale]: http://bealecorner.org/best/RPi/
[forum1]: https://www.raspberrypi.org/forums/viewtopic.php?f=43&t=44918
[forum2]: https://www.raspberrypi.org/forums/viewtopic.php?f=43&t=92562
[dcraw-6by9]: https://github.com/6by9/RPiTest/tree/master/dcraw
[dcraw-pr]: https://github.com/6by9/RPiTest/pull/1
[picamera-raw]: https://picamera.readthedocs.io/en/release-1.10/recipes2.html#bayer-data
[picamera]: https://www.raspberrypi.org/documentation/usage/camera/python/README.md
[12-40mm]: https://www.amazon.com/StarDot-Vari-Focal-Camera-Lens-Black/dp/B00IPR1YSC