Cross-compiling OpenCV for embedded linux

I have compiled OpenCV many times now, since for one reason or another each time I needed to use it the previously compiled files would be lost. This was in addition done directly on the platform, which meant having the board running for 8-10 hours building everything, and not at all fun having to redo, for example for getting locked out of the board by a poor wifi AP configuration and having to re-flash the memory!

So I finally decided to do the sensible thing and cross-compile it, which means using the much higher processing power of my laptop to build the files, but for the target board’s architecture, significantly reducing the time needed (kind of like sending something to the cloud to be processed, but locally). The official OpenCV guide covers the basics, but is oriented to C++, since the files needed for Python development are not generated.

This  meant that I simply would have to link to the Python libs and compile it. I merged the cross-compiler-specific instructions with the regular method I’ve followed other times and… it predictably failed. It turned out that I was trying to build OpenCV for an armhf architecture while linking it to Python libs from my native (and incompatible) x86_64, which of course meant that nothing worked.

After much trial and error I found that I had to link to the correct architecture files, and the best way to do so was copying them from the target board before  the compilation. The full procedure would then look something like this:

  1. Install any missing packages needed and download OpenCV
  2. Copy the required files from the target board
  3. Cross-compile OpenCV
  4. Send the compiled library to the target board

Each stage is completed by a different script, which takes care of intermediate steps such as creating directories as well. There is also a configuration file where the board’s username, IP address and type are specified, and optionally the password too. The board type is arm-linux-gnueabihf for an armhf architecture and x86_64-linux-gnu for x86_64, there will probably be a folder under /usr/include/ with the correct name for other architectures. If the password field is filled, that is what will be used to transfer the files, if left empty the user must manually enter it each time.

The configuration file:

NAME="nanopi"
ADDRESS="192.168.42.1"
ARCHITECTURE="arm-linux-gnueabihf"
# If the password is provided the transfer of files from/to the board will be
# done automatically, otherwise it will be asked for every time it's needed.
PASSWORD=""

Step 1, installing packages and downloading OpenCV:

#!/usr/bin/env bash
# Install libs
echo "----- Installing necessary packages"
sudo apt-get update
sudo apt-get install -y build-essential sshpass cmake pkg-config libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev libgtk2.0-dev libgtk-3-dev libcanberra-gtk* libatlas-base-dev gfortran python2.7-dev python3-dev
sudo pip install numpy
sudo apt-get install -y gcc-arm-linux-gnueabihf  g++-arm-linux-gnueabihf

# Download OpenCV
echo "----- Downloading OpenCV"
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

Step 2, copying files from the board:

#!/usr/bin/env bash
source scripts/config

# Create folder structure
echo "----- Creating python folder structure"
mkdir opencv/python
mkdir opencv/python/lib
mkdir opencv/python/include
mkdir opencv/python/include/python2.7
mkdir opencv/python/include/python3.5m
cd opencv/python

echo "----- Retrieving python files"
if [ -n "${PASSWORD}" ]; then
    # Python config
    sshpass -p ${PASSWORD} scp ${NAME}@${ADDRESS}:/usr/include/${ARCHITECTURE}/python2.7/pyconfig.h include/python2.7/
    sshpass -p ${PASSWORD} scp ${NAME}@${ADDRESS}:/usr/include/${ARCHITECTURE}/python3.5m/pyconfig.h include/python3.5m/
    # Python libraries
    sshpass -p ${PASSWORD} scp ${NAME}@${ADDRESS}:/usr/lib/${ARCHITECTURE}/libpython*.so lib
else
    # Python config
    scp ${NAME}@${ADDRESS}:/usr/include/${ARCHITECTURE}/python2.7/pyconfig.h include/python2.7/
    scp ${NAME}@${ADDRESS}:/usr/include/${ARCHITECTURE}/python3.5m/pyconfig.h include/python3.5m/
    # Python libraries
    scp ${NAME}@${ADDRESS}:/usr/lib/${ARCHITECTURE}/libpython*.so lib
fi

# Copy Python config files
echo "----- Copying imported config files"
sudo mkdir /usr/include/${ARCHITECTURE}
sudo mkdir /usr/include/${ARCHITECTURE}/python2.7
sudo mkdir /usr/include/${ARCHITECTURE}/python3.5m
sudo cp include/python2.7/pyconfig.h /usr/include/${ARCHITECTURE}/python2.7
sudo cp include/python3.5m/pyconfig.h /usr/include/${ARCHITECTURE}/python3.5m

Step 3, cross-compiling:

#!/usr/bin/env bash
echo "----- Creating cross-compiled build directory structure"
cd opencv
mkdir buildCross
cd buildCross
mkdir installation

make clean

echo "----- Setting up compilation"
cmake -D CMAKE_BUILD_TYPE=RELEASE   \
    -D CMAKE_INSTALL_PREFIX=installation \
    -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules   \
    -D CMAKE_TOOLCHAIN_FILE=../platforms/linux/arm-gnueabi.toolchain.cmake ../  \
    -D PYTHON2_INCLUDE_PATH=/usr/include/python2.7  \
    -D PYTHON2_INCLUDE_DIR=../python/include/python2.7    \
    -D PYTHON2_LIBRARY=../python/lib/libpython2.7.so  \
    -D PYTHON2_NUMPY_INCLUDE_DIRS=/usr/local/lib/python2.7/dist-packages/numpy/core/include \
    -D PYTHON3_INCLUDE_PATH=/usr/include/python3.5m \
    -D PYTHON3_INCLUDE_DIR=../python/include/python3.5m    \
    -D PYTHON3_LIBRARY=../python/lib/libpython3.5m.so \
    -D PYTHON3_NUMPY_INCLUDE_DIRS=/usr/lib/python3/dist-packages/numpy/core/include \
    -D BUILD_OPENCV_PYTHON2=ON  \
    -D BUILD_OPENCV_PYTHON3=ON  \
    -D INSTALL_PYTHON_EXAMPLES=OFF  \
    -D BUILD_TESTS=OFF \
    -D BUILD_EXAMPLES=OFF \
    -D ENABLE_NEON=ON  \
    -D ENABLE_VFPV3=ON   \
    -D WITH_TBB=ON \
    -D BUILD_TBB=ON ..

echo "----- Starting compilation"
make -j8
make install

cd installation/lib/python3.5/dist-packages/
mv cv2* cv2.so
echo "----- OpenCV correctly built"

Step 4, sending compiled files to the target board:

#!/usr/bin/env bash
source scripts/config

# Send compiled OpenCV to embedded board
echo "----- Sending compiled files to board"
if [ -n "${PASSWORD}" ]; then
    sshpass -p ${PASSWORD} scp -r opencv/buildCross/installation/  ${NAME}@${ADDRESS}:~/
else
    scp -r opencv/buildCross/installation/  ${NAME}@${ADDRESS}:~/
fi

I also wrote a small script that calls all the previous ones sequentially so everything can be done in one go:

#!/usr/bin/env bash

chmod +x scripts/*.sh

scripts/downloadPackages.sh
scripts/importFiles.sh
scripts/crossCompile.sh
scripts/sendOpenCV.sh

echo "----- To complete installation, log into the target board and execute the command: "
echo "sudo rsync -av installation/ /usr/local"

To launch this, make sure that the board and computer are both connected to the Internet and on the same network, and execute

chmod +x fullProcess.sh && ./fullProcess.sh

Once the installation folder containing the compiled library has been transferred to the board, log into it and execute

sudo rsync -av installation/ /usr/local

to copy the different files to the corresponding places.

If when trying to import cv2 there is an error about not finding the corresponding GLIBXX version in the board as the one used to compile, try upgrading g++ to the appropiate version. For example, for GLIBCXX_3.4.22 execute

sudo apt upgrade g++-6

The complete process will vary depending on processing speed, Internet connection and packages already present, but in my case took just under 30 minutes from launching the script to receiving the folder in the board.

https://github.com/alvaroferran/OpenCV_CrossInstaller

Leave a Reply

Your email address will not be published. Required fields are marked *