The Quest of Building OpenCV 3.2.0 Debian Packages on Raspberry Pi Zero W

The newest members in my toy collection are a Raspberry Pi Zero W (paid link) and a NoIR Camera Module (paid link), purchased in Dec 2017. Recently, I witnessed an impressive Contour Detection demo at MoCoMakers meetup. I read their source code, and it has a dependency on cv2 Python package. Therefore, the first step to get it working on my RPi Zero would be installing OpenCV that provides cv2 package.

While Raspbian Stretch offers a python-opencv package, it is version 2.4.9 released in 2014, and it only works with Python 2 but not Python 3. Since I'm starting from scratch, I wanted to develop on newer platforms: OpenCV 3.x and Python 3.

Many online tutorials suggest compiling OpenCV from source code. There are also a few sites offering pre-compiled tarballs, but these are either compiled for the Raspberry Pi 3, or built for Raspbian Jessie; neither would be compatible with my Raspberry Pi Zero W running Raspbian Stretch. Therefore, I started my quest to build OpenCV 3 for Pi Zero.

Debian Package > Source Code

When I first learned Linux, the standard process of installing software is wget, tar xzvf, ./configure, make, make install. Today, this is no longer recommended because software installed this way is difficult to remove and could cause conflicts. Instead, it is recommended to install everything from Debian packages.

OpenCV 3 does have Debian packages, but they are intended for Debian Buster. There isn't a Raspbian Buster yet.

It is, however, possible to build a package intended for a newer Debian version onto an older Debian release. This process is known as building a backport. I decided to do things in the proper way, and build backport packages instead of installing from OpenCV 3 source code.

ARM64 is not "armhf"

Pi Zero has one CPU core and 512MB of memory. It would take a long time to build OpenCV 3, a large and complex software package. I decided to build it in the cloud, and let a Scaleway instance do the heavy work.

I created a virtual machine with 4 dedicated ARM64 CPU cores and 2GB memory, at the cost of €0.04 per hour with IPv6-only connectivity. Following the official guide for building a private backport, it took about 20 hours to produce 66 .deb package files. I noticed something wrong as I was downloading them to the local storage: they are suffixed with _arm64.deb rather than the familiar _armhf.deb.

ARM64 is a different CPU architecture from 32-bit "armhf" CPU, and the packages would not be compatible.

"armhf" CPUs are Not Created Equal

Realizing my ARM64 mistake, I provisioned a "bare metal" C1 instance on Scaleway. They have 32-bit "armhf" CPU cores, and I previously built software for my Raspberry Pi 3 on these instances. It costs slightly more at €0.06 per hour, because an IPv4 address is required to access it.

As I noticed during the previous attempt, the fakeroot debian/rules binary step was not strictly necessary, so I skipped it this time. It still required an overnight job (15 hours) to finish the build. I joyfully downloaded 66 _armhf.deb package files, and installed them into my Pi Zero.

I eagerly typed import cv2 into the Python 3 console, but received an error message:

Illegal instruction.

After some GDB and searching, I learned that, the Raspberry Pi Zero has an ARMv6 CPU, while Scaleway's C1 and the Pi 3 have ARMv7 CPUs. Although they are both labeled "armhf", ARMv7 architecture supports more CPU instructions than ARMv6. As a result, software compiled for ARMv7 would contain instructions unavailable on ARMv6 and therefore cause "Illegal instruction" error.

Building on the Pi Zero in 3 Days

Unfortunately there isn't a cloud provider offering ARMv6 instances, and I am not well-versed on the art of cross-compiling. I was left with no choice but to build OpenCV 3 on the Pi Zero itself. To reduce wear-and-tear on the microSD card, I plugged in a 16GB USB flash drive via a USB OTG cable (paid link), to store the source code, build files, and a 1GB swapfile.

The build progressed to 12% overnight, and then I kicked loose the power cord. After restarting the build from the beginning, I left the Pi Zero alone and avoided coming too close to it. It took 56 hours for dpkg-buildpackage -us -uc to terminate, with a bloody error:

stdin not open for reading!
stdin not open for reading!
stdin not open for reading!
dh_installman: man --recode UTF-8 ./opencv_createsamples\.1 > opencv_createsampl
es\.1\.new returned exit code 2
debian/rules:71: recipe for target 'binary' failed
make: *** [binary] Error 2
dpkg-buildpackage: error: fakeroot debian/rules binary gave error exit status 2

Although it is obvious where this error message comes from, and I have seen it several times building other packages, I do not know how to fix it. Therefore, I used a quick and dirty workaround:

if ! [[ -f /usr/bin/dh_installman.real ]]; then
  mv /usr/bin/dh_installman /usr/bin/dh_installman.real
  (echo '#!/bin/bash'; echo '/usr/bin/dh_installman.real "$@" || true') > /usr/bin/dh_installman
  chmod +x /usr/bin/dh_installman

This replaces dh_installman with a bash script that returns "true" even if the actual program has failed.

With this workaround in place, I restarted the package building process. 56 hours of work did not go down the drain, however, because I added the --no-pre-clean flag this time, which allows dpkg-buildpackage to use the "dirty" source code directory and pick up from where it left off. It took another 10 hours to finish the packaging process.

I installed the packages, and fidgetingly typed import cv2 into the Python 3 console. It succeeded. I made the contour detection algorithm working a few days later.

You can Install OpenCV 3 in 15 Minutes

After a happy dance, I uploaded my OpenCV 3 packages to a Bintray repository to share with everyone. Now you can follow my instructions and install OpenCV 3 on Raspberry Pi Zero W in 15 minutes.