RPi HQ Camera: 4.8 mm Computar Video Lens

The Big Box o’ Optics disgorged an ancient new-in-box Computar 4.8 mm lens, originally intended for a TV camera, with a C mount perfectly suited for the Raspberry Pi HQ camera:

RPi HQ Camera - Computar 4.8mm - front view
RPi HQ Camera – Computar 4.8mm – front view

Because it’s a video lens, it includes an aperture driver expecting a video signal from the camera through a standard connector:

Computar 4.8 mm lens - camera plug
Computar 4.8 mm lens – camera plug

The datasheet tucked into the box (!) says it expects 8 to 16 V DC on the red wire (with black common) and video on white:

Computar Auto Iris TV Lens Manual
Computar Auto Iris TV Lens Manual

Fortunately, applying 5 V to red and leaving white unconnected opens the aperture all the way. Presumably, the circuitry thinks it’s looking at a really dark scene and isn’t fussy about the missing sync pulses.

Rather than attempt to find / harvest a matching camera connector, the cord now terminates in a JST plug, with the matching socket hot-melt glued to the Raspberry Pi case:

RPi HQ Camera - 4.8 mm Computar lens - JST power
RPi HQ Camera – 4.8 mm Computar lens – JST power

The Pi has +5 V and ground on the rightmost end of its connector, so the Computar lens will be jammed fully open.

I gave it something to look at:

RPi HQ Camera - Computar 4.8mm - overview
RPi HQ Camera – Computar 4.8mm – overview

With the orange back plate about 150 mm from the RPi, the 4.8 mm lens delivers this scene:

RPi HQ Camera - 4.8 mm Computar lens - 150mm near view
RPi HQ Camera – 4.8 mm Computar lens – 150mm near view

The focus is on the shutdown / startup button just to the right of the heatsink, so the depth of field is maybe 25 mm front-to-back.

For comparison, the official 16 mm lens stopped down to f/8 has a tighter view with good depth of field:

RPi HQ Camera - 16 mm lens - 150mm near view
RPi HQ Camera – 16 mm lens – 150mm near view

It’d be nice to have a variable aperture, but it’s probably not worth the effort.

Arducam Motorized Focus Camera Control

Despite the company name name, the Arducam 5 MP Motorized Focus camera plugs into a Raspberry Pi’s camera connector and lives on a PCB the same size as ordinary RPi cameras:

Arducam Motorized Focus RPi Camera - test overview
Arducam Motorized Focus RPi Camera – test overview

That’s a focus test setup to get some idea of how the control values match up against actual distances.

It powers up focused at infinity (or maybe a bit beyond):

Arducam Motorized Focus RPi Camera - default focus
Arducam Motorized Focus RPi Camera – default focus

In practice, it’s a usable, if a bit soft, at any distance beyond a couple of meters.

The closest focus is around 40 mm, depending on where you set the ruler’s zero point:

Arducam Motorized Focus RPi Camera - near focus
Arducam Motorized Focus RPi Camera – near focus

That’s the back side of the RPi V1 camera PCB most recently seen atop the mystery microscope objective illuminator.

Pondering the sample code shows the camera focus setting involves writing two bytes to an I²C address through the video controller’s I²C bus. Enable that bus with a line in /boot/config.txt:

dtparam=i2c_vc=on

If you’re planning to capture 1280×720 or larger still images, reserve enough memory in the GPU:

gpu_mem=512

I don’t know how to determine the correct value.

And, if user pi isn’t in group i2c, make it so, then reboot.

The camera must be running before you can focus it, so run raspivid and watch the picture. I think you must do that in order to focus a (higher-res) still picture, perhaps starting a video preroll (not that kind) in a different thread while you fire off a (predetermined?) focus value, allow time for the lens to settle, then acquire a still picture with the video still running.

The focus value is number between 0 and 1023, in two bytes divided, written in big-endian order to address 0x0c on bus 0:

i2cset -y 0 0x0c 0x3f 0xff

You can, of course, use decimal numbers:

i2cset -y 0 0x0c 63 255

I think hex values are easier to tweak by hand.

Some tinkering gives this rough correlation:

Focus value (hex)Focus distance (mm)
3FFF45 (-ish)
300055
200095
1000530
0800850
Arducam Motorized Focus Camera – numeric value vs mm

Beyond a meter, the somewhat gritty camera resolution gets in the way of precise focusing, particularly in low indoor lighting.

A successful write produces a return code of 0. Sometimes the write will inexplicably fail with an Error: Write failed message, a return code of 1, and no focus change, so it’s Good Practice to retry until it works.

This obviously calls for a knob and a persistent value!

Raspberry Pi HQ Camera + 16 MM 10 MP Lens: Depth of Field

Part of the motivation for getting a Raspberry Pi HQ camera sensor was being able to use lenses with adjustable focus and aperture, like the Official 10 MP “telephoto” lens:

RPi HQ Camera - aperture demo setup
RPi HQ Camera – aperture demo setup

Yes, it can focus absurdly close to the lens, particularly when you mess around with the back focus adjustment.

With the aperture fully open at f/1.4:

RPi HQ Camera - aperture demo - f 1.4
RPi HQ Camera – aperture demo – f 1.4

Stopped down to f/16:

RPi HQ Camera - aperture demo - f 16
RPi HQ Camera – aperture demo – f 16

The field of view is about 60 mm (left-to-right) at 150 mm. Obviously, arranging the camera with its optical axis more-or-less perpendicular to the page will improve everything about the image.

For normal subjects at normal ranges with normal lighting, the depth of field works pretty much the way you’d expect:

At f/1.4, focused on the potted plants a dozen feet away:

Raspberry Pi HQ Camera - outdoor near focus
Raspberry Pi HQ Camera – outdoor near focus

Also at f/1.4, focused on the background at infinity:

Raspberry Pi HQ Camera - outdoor far focus
Raspberry Pi HQ Camera – outdoor far focus

In comparison, the laptop camera renders everything equally badly (at a lower resolution, so it’s not a fair comparison):

Laptop camera - outdoors
Laptop camera – outdoors

Stipulated: these are screen captures of “movies” from raspivid over the network to VLC. The HQ sensor is capable of much better images.

None of this is surprising, but it’s a relief from the usual phone sensor camera with fixed focus (at “infinity” if you’re lucky) and a wide-open aperture.

Mystery Microscope Objective Illuminator

Rummaging through the Big Box o’ Optics in search of something else produced this doodad:

Microscope objective illuminator - overview
Microscope objective illuminator – overview

It carries no brand name or identifier, suggesting it was shop-made for a very specific and completely unknown purpose. The 5× objective also came from the BBo’O, but wasn’t related in any way other than fitting the threads, so the original purpose probably didn’t include it.

The little bulb fit into a cute and obviously heat-stressed socket:

Microscope objective illuminator - bulb detail
Microscope objective illuminator – bulb detail

The filament was, of course, broken, so I dismantled the socket and conjured a quick-n-dirty white LED that appears blue under the warm-white bench lighting:

Microscope objective illuminator - white LED
Microscope objective illuminator – white LED

The socket fits into the housing on the left, which screws onto a fitting I would have sworn was glued / frozen in place. Eventually, I found a slotted grub screw hidden under a glob of dirt:

Microscope objective illuminator - lock screw
Microscope objective illuminator – lock screw

Releasing the screw let the fitting slide right out:

Microscope objective illuminator - lamp reflector
Microscope objective illuminator – lamp reflector

The glass reflector sits at 45° to direct the light coaxially down into the objective (or whatever optics it was originally intended for), with the other end of the widget having a clear view straight through. I cleaned the usual collection of fuzz & dirt off the glass, then centered and aligned the reflection with the objective.

Unfortunately, the objective lens lacks antireflection coatings:

Microscope objective illuminator - stray light
Microscope objective illuminator – stray light

The LED tube is off to the right at 2 o’clock, with the bar across the reflector coming from stray light bouncing back from the far wall of the interior. The brilliant dot in the middle comes from light reflected off the various surfaces inside the objective.

An unimpeachable source tells me microscope objectives are designed to form a real image 180 mm up inside the ‘scope tube with the lens at the design height above the object. I have the luxury of being able to ignore all that, so I perched a lensless Raspberry Pi V1 camera on a short brass tube and affixed it to a three-axis positioner:

Microscope objective illuminator - RPi camera lashup
Microscope objective illuminator – RPi camera lashup

A closer look at the lashup reveals the utter crudity:

Microscope objective illuminator - RPi camera lashup - detail
Microscope objective illuminator – RPi camera lashup – detail

It’s better than I expected:

Microscope objective illuminator - RPi V1 camera image - unprocessed
Microscope objective illuminator – RPi V1 camera image – unprocessed

What you’re seeing is the real image formed by the objective lens directly on the RPi V1 camera’s sensor: in effect, the objective replaces the itsy-bitsy camera lens. It’s a screen capture from VLC using V4L2 loopback trickery.

Those are 0.1 inch squares printed on the paper, so the view is about 150×110 mil. Positioning the camera further from the objective would reduce both the view (increase the magnification) and the amount of light, so this may be about as good as it get.

The image started out with low contrast from all the stray light, but can be coerced into usability:

Microscope objective illuminator - RPi V1 camera image - auto-level adjust
Microscope objective illuminator – RPi V1 camera image – auto-level adjust

The weird violet-to-greenish color shading apparently comes from the lens shading correction matrix baked into the RPi image capture pipeline and can, with some difficulty, be fixed if you have a mind to do so.

All this is likely not worth the effort given the results of just perching a Pixel 3a atop the stereo zoom microscope:

Pixel 3a on stereo zoom microscope
Pixel 3a on stereo zoom microscope

But I just had to try it out.

Raspberry Pi HQ Camera Mount

As far as I can tell, Raspberry Pi cases are a solved problem, so 3D printing an intricate widget to stick a Pi on the back of an HQ camera seems unnecessary unless you really, really like solid modeling, which, admittedly, can be a thing. All you really need is a simple adapter between the camera PCB and the case of your choice:

HQ Camera Backplate - OpenSCAD model
HQ Camera Backplate – OpenSCAD model

A quartet of 6 mm M2.5 nylon spacers mount the adapter to the camera PCB:

RPi HQ Camera - nylon standoffs
RPi HQ Camera – nylon standoffs

The plate has recesses to put the screw heads below the surface. I used nylon screws, but it doesn’t really matter.

The case has all the right openings, slots in the bottom for a pair of screws, and costs six bucks. A pair of M3 brass inserts epoxied into the plate capture the screws:

RPi HQ Camera - case adapter plate - screws
RPi HQ Camera – case adapter plate – screws

Thick washers punched from an old credit card go under the screws to compensate for the case’s silicone bump feet. I suppose Doing the Right Thing would involve 3D printed spacers matching the cross-shaped case cutouts.

Not everyone agrees with my choice of retina-burn orange PETG:

RPi HQ Camera - 16 mm lens - case adapter plate
RPi HQ Camera – 16 mm lens – case adapter plate

Yes, that’s a C-mount TV lens lurking in the background, about which more later.

The OpenSCAD source code as a GitHub Gist:

// Raspberry Pi HQ Camera Backplate
// Ed Nisley KE4ZNU 2020-09
//-- Extrusion parameters
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
ID = 0;
OD = 1;
LENGTH = 2;
//- Basic dimensions
CamPCB = [39.0,39.0,1.5]; // Overall PCB size, plus a bit
CornerRound = 3.0; // ... has rounded corners
CamScrewOC = [30.0,30.0,0]; // ... mounting screw layout
CamScrew = [2.5,5.0,2.2]; // ... LENGTH = head thickness
Standoff = [2.5,5.5,6.0]; // nylon standoffs
Insert = [3.0,4.0,4.0];
WallThick = IntegerMultiple(2.0,ThreadWidth);
PlateThick = Insert[LENGTH];
CamBox = [CamPCB.x + 2*WallThick,
CamPCB.y + 2*WallThick,
Standoff.z + PlateThick + CamPCB.z + 1.0];
PiPlate = [90.0,60.0,PlateThick];
PiPlateOffset = [0.0,(PiPlate.y - CamBox.y)/2,0];
PiSlotOC = [0.0,40.0];
PiSlotOffset = [3.5,3.5];
NumSides = 2*3*4;
TextDepth = 2*ThreadThick;
//----------------------
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
}
//----------------------
// Build it
difference() {
union() {
hull() // camera enclosure
for (i=[-1,1], j=[-1,1])
translate([i*(CamBox.x/2 - CornerRound),j*(CamBox.y/2 - CornerRound),0])
cylinder(r=CornerRound,h=CamBox.z,$fn=NumSides);
translate(PiPlateOffset)
hull()
for (i=[-1,1], j=[-1,1]) // Pi case plate
translate([i*(PiPlate.x/2 - CornerRound),j*(PiPlate.y/2 - CornerRound),0])
cylinder(r=CornerRound,h=PiPlate.z,$fn=NumSides);
}
hull() // camera PCB space
for (i=[-1,1], j=[-1,1])
translate([i*(CamPCB.x/2 - CornerRound),j*(CamPCB.y/2 - CornerRound),PlateThick])
cylinder(r=CornerRound,h=CamBox.z,$fn=NumSides);
translate([0,-CamBox.y/2,PlateThick + CamBox.z/2])
cube([CamScrewOC.x - Standoff[OD],CamBox.y,CamBox.z],center=true);
for (i=[-1,1], j=[-1,1]) // camera screws with head recesses
translate([i*CamScrewOC.x/2,j*CamScrewOC.y/2,-Protrusion]) {
PolyCyl(CamScrew[ID],2*CamBox.z,6);
PolyCyl(CamScrew[OD],CamScrew[LENGTH] + Protrusion,6);
}
for (j=[-1,1]) // Pi case screw inserts
translate([0,j*PiSlotOC.y/2 + PiSlotOffset.y,-Protrusion] + PiPlateOffset)
PolyCyl(Insert[OD],2*PiPlate.z,6);
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4,0,PlateThick - TextDepth/2] + PiPlateOffset)
cube([15.0,30.0,TextDepth + Protrusion],center=true);
}
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4 + 3,0,PlateThick - TextDepth - Protrusion] + PiPlateOffset)
linear_extrude(height=TextDepth + Protrusion,convexity=2)
rotate(-90)
text("Ed Nisley",font="Arial:style=Bold",halign="center",valign="center",size=4,spacing=1.05);
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4 - 3,0,PlateThick - TextDepth - Protrusion] + PiPlateOffset)
linear_extrude(height=TextDepth + Protrusion,convexity=2)
rotate(-90)
text("KE4ZNU",font="Arial:style=Bold",halign="center",valign="center",size=4,spacing=1.05);

Raspberry Pi Streaming Video Loopback

As part of spiffing my video presence for SquidWrench Zoom meetings, I put a knockoff RPi V1 camera into an Az-El mount, stuck it to a Raspberry Pi, installed the latest OS Formerly Known as Raspbian, did a little setup, and perched it on the I-beam over the workbench:

Raspberry Pi - workbench camera setup
Raspberry Pi – workbench camera setup

The toothbrush head has a convenient pair of neodymium magnets affixing the RPi’s power cable to the beam, thereby preventing the whole lashup from falling off. The Pi, being an old Model B V 1.1, lacks onboard WiFi and requires a USB WiFi dongle. The white button at the lower right of the heatsink properly shuts the OS down and starts it up again.

Zoom can show video only from video devices / cameras attached to the laptop, so the trick is to make video from the RPi look like it’s coming from a local laptop device.

Start by exporting video from the Raspberry Pi:

raspivid --nopreview -t 0 -rot 180 -awb sun --sharpness -50 --flicker 60hz -w 1920 -h 1080 -ae 48 -a 1032 -a 'RPi Cam1 %Y-%m-%d %X'  -b 1000000 -l -o tcp://0.0.0.0:5000

The -rot 180 -awb sun --sharpness -50 --flicker 60hz parameters make the picture look better. The bottom of the video image There is no way to predict which side of the video will be on the same side as the cable, if that’s any help figuring out which end is up, and the 6500 K LED tubes apparently fill the shop with “sun”.

The -l parameter causes raspivid to wait until it gets an incoming tcp connection on port 5000 from any other IP address, whereupon it begins capturing video and sending it out.

Then, on the laptop, create a V4L loopback device:

sudo modprobe v4l2loopback devices=1 video_nr=10 exclusive_caps=1 card_label="Workbench"

Zoom will then include a video source identified as “Workbench” in its list of cameras.

Now fetch video from the RPi and ram it into the loopback device:

ffmpeg -f h264 -i tcp://192.168.1.50:5000 -f v4l2 -pix_fmt yuv420p /dev/video10

VLC knows it as /dev/video10:

RPi - V4L loopback - screen grab
RPi – V4L loopback – screen grab

That’s the edge of the workbench over there on the left, looking distinctly like a cliff.

The RPi will happily stream video all day long to ffmpeg while you start / stop the display program pulling the bits from the video device. However, killing ffmpeg also kills raspivid, requiring a manual restart of both programs. This isn’t a dealbreaker for my simple needs, but it makes unattended streaming from, say, a yard camera somewhat tricky.

There appear to be an infinite number of variations on this theme, not all of which work, and some of which rest upon an unsteady ziggurat of sketchy / unmaintained software.

Addendum: If you have a couple of RPi cameras, it’s handy to run the matching ssh and ffmpeg sessions in screen / tmux / whatever terminal multiplexer you prefer. I find it easier to flip through those sessions with Ctrl-A N, rather than manage half a dozen tabs in a single terminal window. Your mileage may differ.

PiHole with DNS-over-HTTP: Revised

More than a year later, the PiHole continues to work fine, but the process for installing the Cloudflare DoH machinery has evolved.

(And, yes, it’s supposed to be DNS-over-HTTPS. So it goes.)

To forestall link rot, the key points:

cd /tmp ;  wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-arm.tgz
tar -xvzf cloudflared-stable-linux-arm.tgz 
sudo cp cloudflared /usr/local/bin
sudo chmod +x /usr/local/bin/cloudflared
sudo cloudflared -v
sudo useradd -s /usr/sbin/nologin -r -M cloudflared
sudo nano /etc/default/cloudflared
----
CLOUDFLARED_OPTS=--port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query 
----
sudo chown cloudflared:cloudflared /etc/default/cloudflared
sudo chown cloudflared:cloudflared /usr/local/bin/cloudflared
sudo nano /etc/systemd/system/cloudflared.service
----
[Unit]
Description=cloudflared DNS over HTTPS proxy
After=syslog.target network-online.target

[Service]
Type=simple
User=cloudflared
EnvironmentFile=/etc/default/cloudflared
ExecStart=/usr/local/bin/cloudflared proxy-dns $CLOUDFLARED_OPTS
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target
----
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared

Then aim PiHole’s DNS at 127.0.0.1#5053. It used to be on port #54, for whatever that’s worth.

Verify it at https://1.1.1.1/help, which should tell you DoH is in full effect.

To update the daemon, which I probably won’t remember:

wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-arm.tgz
tar -xvzf cloudflared-stable-linux-arm.tgz
sudo systemctl stop cloudflared
sudo cp ./cloudflared /usr/local/bin
sudo chmod +x /usr/local/bin/cloudflared
sudo systemctl start cloudflared
cloudflared -v
sudo systemctl status cloudflared

And then It Just Works … again!