Tina5 Linux開発環境の構築とカスタマイズ

開発環境の準備

Ubuntu 20.04、18.04、16.04、14.04 の仮想マシンまたは物理マシンを用意してください。他のオペレーティングシステムはテスト対象外であり、問題が発生してもサポート対象外です。

システムの更新と基本パッケージのインストールを行います。

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install build-essential subversion git libncurses5-dev zlib1g-dev gawk flex bison quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip lsof python3 python2 python3-dev android-tools-mkbootimg python2 libpython3-dev

i386アーキテクチャの追加

SDK内に32ビットのバイナリが含まれているため、i386アーキテクチャのサポートを有効化する必要があります。これを行わないと実行時に「Segmentation fault」が発生します。

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt install gcc-multilib
sudo apt install libc6:i386 libstdc++6:i386 lib32z1

AWOL Tina Linux BSPの取得

AWOLアカウントの登録

SDKのダウンロードには AWOLフォーラム のアカウントが必要です。アカウントレベルがLV2以上であることが必須です。以下のスレッドで4回の投稿を行うことで容易に達成可能です:
https://bbs.aw-ol.com/topic/4158/share/1

repo管理ツールの導入

BSPは repo ツールを使用して取得します。国内ユーザー向けにミラーサーバー経由でインストールすることを推奨します。

mkdir -p ~/.bin
PATH="${HOME}/.bin:${PATH}"
curl https://mirrors.bfsu.edu.cn/git/git-repo > ~/.bin/repo
chmod a+rx ~/.bin/repo

上記は一時的なインストール方法です。ターミナル再起動後に再実行が必要です。永続的に設定するには、シェルの初期化ファイル(例:~/.bashrc)に以下を追加してください。

PATH="${HOME}/.bin:${PATH}"

また、repo を使用する際にはアップデートチェックによる遅延が発生する可能性があります。これを回避するため、環境変数 REPO_URL を設定します。

echo export REPO_URL='https://mirrors.bfsu.edu.cn/git/git-repo' >> ~/.bashrc
source ~/.bashrc

使用しているシェルが dashzsh などの場合、対応する設定ファイルに該当する環境変数を定義してください。

Git認証情報の保存設定

毎回パスワードを入力しなくて済むように、認証情報を自動保存する設定を行います。

git config --global credential.helper store

SDKのディレクトリ作成と初期化

SDKを格納するためのディレクトリを作成し、移動します。

mkdir tina-v853-open
cd tina-v853-open

repoの初期化

リポジトリの初期化を行います。V853Tina_Openのマニフェストを指定します。

repo init -u https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git -b master -m tina-v853-open.xml

SDKの同期

repo sync

開発ブランチの作成

repo start devboard-v853-tina-for-awol --all

TinyVisionボードへの対応

デフォルトでは V853-Vision 開発ボードのみに対応しています。独自のボード(TinyVision)を追加するには、以下の手順を実施します。

バージョン ダウンロードリンク
1.0 tina-bsp-tinyvision.tar.gz
1.1 tina-bsp-tinyvision.tar.gz
1.2 tina-bsp-tinyvision.tar.gz

ダウンロードしたファイルをSDKのルートディレクトリに配置し、展開します。

tar xvf tina-bsp-tinyvision.tar.gz

SDK環境の初期化

開発を開始する前に、環境を初期化する必要があります。

source build/envsetup.sh

その後、メニューから「TinyVision」を選択します。

ISP機能のカスタマイズ

SDK内の libAWIspApi はV85xシリーズに対応していません。以下の手順で対応を追加します。

openwrt/package/allwinner/vision/libAWIspApi/machinfo ディレクトリに新しいフォルダ v851se を作成し、その中に build.mk を作成します。

ISP_DIR:=isp600

同様に v851sv853 でも同様の操作が可能です。その後、m menuconfig で該当パッケージを有効化します。

camerademoによるカメラテスト

以下の設定を m menuconfig で行います。

Allwinner  --->
  Vision  --->
    <*> camerademo........................................ camerademo test sensor  --->
      [*]   Enabel vin isp support

システムをビルド・焼入れ後、camerademo コマンドを実行することで、正常に写真撮影が可能になります。

板載SD NANDの設定

デバイスツリーの &sdc2 セクションを以下のように編集します。

&sdc2 {
    non-removable;
    bus-width = <4>;
    mmc-ddr-3_3v;
    /*mmc-hs200-1_8v;*/
    /*mmc-hs400-1_8v;*/
    no-sdio;
    /delete-property/ no-sd;
    no-mmc;
    ctl-spec-caps = <0x8>;
    cap-mmc-highspeed;
    sunxi-signal-vol-sw-without-pmu;
    sunxi-power-save-mode;
    /*sunxi-dis-signal-vol-sw;*/
    max-frequency = <50000000>;
    /*vmmc-supply = <&reg_dcdc1>;*/
    /*emmc io vol 3.3v*/
    /*vqmmc-supply = <&reg_aldo1>;*/
    /*emmc io vol 1.8v*/
    /*vqmmc-supply = <&reg_eldo1>;*/
    status = "okay";
};

変更後、線形刷り込み(line flash)で適用できます。

OpenCVの導入とカスタマイズ

OpenCVパッケージの有効化

m menuconfig で以下の設定を有効化します。

OpenCV  --->
  <*> opencv....................................................... opencv libs
  [*]   Enabel sunxi vin isp support

多平面キャプチャの対応

OpenCVのV4L2実装は V4L2_BUF_TYPE_VIDEO_CAPTURE を前提としていますが、Sunxi-VINドライバは V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE を使用しており、互換性がありません。以下の修正により、適切なタイプを選択できるようになります。

-    form.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    form.fmt.pix.pixelformat = palette;
-    form.fmt.pix.field       = V4L2_FIELD_ANY;
-    form.fmt.pix.width       = width;
-    form.fmt.pix.height      = height;
+    if (capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
+        form.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+        form.fmt.pix.pixelformat = palette;
+        form.fmt.pix.field       = V4L2_FIELD_NONE;
+        form.fmt.pix.width       = width;
+        form.fmt.pix.height      = height;
+    } else if (capability.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
+        form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+        form.fmt.pix_mp.width = width;
+        form.fmt.pix_mp.height = height;
+        form.fmt.pix_mp.pixelformat = palette;
+        form.fmt.pix_mp.field = V4L2_FIELD_NONE;
+    }

RAWセンサー対応の追加

RAWセンサーからのデータ取得を可能にするために、以下の関数を追加します。

#ifdef __USE_VIN_ISP__
bool CvCaptureCAM_V4L::RAWSensor()
{
    struct v4l2_control ctrl;
    struct v4l2_queryctrl qc_ctrl;

    memset(&ctrl, 0, sizeof(struct v4l2_control));
    memset(&qc_ctrl, 0, sizeof(struct v4l2_queryctrl));
    ctrl.id = V4L2_CID_SENSOR_TYPE;
    qc_ctrl.id = V4L2_CID_SENSOR_TYPE;

    if (-1 == ioctl(deviceHandle, VIDIOC_QUERYCTRL, &qc_ctrl)) {
        fprintf(stderr, "V4L2: %s QUERY V4L2_CID_SENSOR_TYPE failed\n", deviceName.c_str());
        return false;
    }

    if (-1 == ioctl(deviceHandle, VIDIOC_G_CTRL, &ctrl)) {
        fprintf(stderr, "V4L2: %s G_CTRL V4L2_CID_SENSOR_TYPE failed\n", deviceName.c_str());
        return false;
    }

    return ctrl.value == V4L2_SENSOR_TYPE_RAW;
}
#endif

さらに、streaming() 関数に以下を追加:

#ifdef __USE_VIN_ISP__
    RawSensor = RAWSensor();

    if (startStream && RawSensor) {
        int VideoIndex = -1;
        sscanf(deviceName.c_str(), "/dev/video%d", &VideoIndex);
        IspPort = CreateAWIspApi();
        IspId = -1;
        IspId = IspPort->ispGetIspId(VideoIndex);
        if (IspId >= 0)
            IspPort->ispStart(IspId);
    } else if (RawSensor && IspId >= 0 && IspPort) {
        IspPort->ispStop(IspId);
        DestroyAWIspApi(IspPort);
        IspPort = NULL;
        IspId = -1;
    }
#endif

OpenCVによるカメラ画像表示

簡単なテスト

このサンプルは tina-bsp-tinyvision.tar.gz に含まれています。以下を設定します。

OpenCV  --->
  <*> opencv....................................................... opencv libs
  [*]   Enabel sunxi vin isp support
  <*> opencv_camera.............................opencv_camera and display image

ソースコードの説明

以下は、OpenCVでカメラ映像を取得し、フレームバッファに描画するプログラムの例です。

#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fb.h>
#include <signal.h>
#include <stdint.h>
#include <sys/ioctl.h>

#include <opencv2/opencv.hpp>

#define DISPLAY_X 240
#define DISPLAY_Y 240

static cv::VideoCapture cap;

struct framebuffer_info {
    uint32_t bits_per_pixel;
    uint32_t xres_virtual;
};

struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path)
{
    struct framebuffer_info info;
    struct fb_var_screeninfo screen_info;
    int fd = -1;
    fd = open(framebuffer_device_path, O_RDWR);
    if (fd >= 0) {
        if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) {
            info.xres_virtual = screen_info.xres_virtual;
            info.bits_per_pixel = screen_info.bits_per_pixel;
        }
    }
    return info;
}

/* Signal handler */
static void terminate(int sig_no)
{
    printf("Got signal %d, exiting ...\n", sig_no);
    cap.release();
    exit(1);
}

static void install_sig_handler(void)
{
    signal(SIGBUS, terminate);
    signal(SIGFPE, terminate);
    signal(SIGHUP, terminate);
    signal(SIGILL, terminate);
    signal(SIGINT, terminate);
    signal(SIGIOT, terminate);
    signal(SIGPIPE, terminate);
    signal(SIGQUIT, terminate);
    signal(SIGSEGV, terminate);
    signal(SIGSYS, terminate);
    signal(SIGTERM, terminate);
    signal(SIGTRAP, terminate);
    signal(SIGUSR1, terminate);
    signal(SIGUSR2, terminate);
}

int main(int, char**)
{
    const int frame_width = 480;
    const int frame_height = 480;
    const int frame_rate = 30;

    install_sig_handler();

    framebuffer_info fb_info = get_framebuffer_info("/dev/fb0");

    cap.open(0);

    if (!cap.isOpened()) {
        std::cerr << "Could not open video device." << std::endl;
        return 1;
    }

    std::cout << "Successfully opened video device." << std::endl;
    cap.set(cv::CAP_PROP_FRAME_WIDTH, frame_width);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, frame_height);
    cap.set(cv::CAP_PROP_FPS, frame_rate);

    std::ofstream ofs("/dev/fb0");

    cv::Mat frame;
    cv::Mat trams_temp_fream;
    cv::Mat yuv_frame;

    while (true) {
        cap >> frame;
        if (frame.depth() != CV_8U) {
            std::cerr << "Not 8 bits per pixel and channel." << std::endl;
        } else if (frame.channels() != 3) {
            std::cerr << "Not 3 channels." << std::endl;
        } else {
            cv::transpose(frame, frame);
            cv::flip(frame, frame, 0);
            cv::resize(frame, frame, cv::Size(DISPLAY_X, DISPLAY_Y));
            int framebuffer_width = fb_info.xres_virtual;
            int framebuffer_depth = fb_info.bits_per_pixel;
            cv::Size2f frame_size = frame.size();
            cv::Mat framebuffer_compat;
            switch (framebuffer_depth) {
            case 16:
                cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 2);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 2);
                }
                break;
            case 32: {
                std::vector<cv::Mat> split_bgr;
                cv::split(frame, split_bgr);
                split_bgr.push_back(cv::Mat(frame_size, CV_8UC1, cv::Scalar(255)));
                cv::merge(split_bgr, framebuffer_compat);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 4);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 4);
                }
            } break;
            default:
                std::cerr << "Unsupported depth of framebuffer." << std::endl;
            }
        }
    }

    return 0;
}

Python3によるOpenCV操作

OpenCV-Python3の有効化

m menuconfig で以下の設定を有効化します。

OpenCV  --->
  <*> opencv....................................................... opencv libs
  [*]   Enabel sunxi vin isp support
  [*]   Enabel opencv python3 binding support

ビルドは非常に時間がかかります。16GB以上のメモリが必要です。

以下は同等の動作を行うPythonスクリプトです。

import cv2
import numpy as np

DISPLAY_X = 240
DISPLAY_Y = 240

frame_width = 480
frame_height = 480
frame_rate = 30

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("Could not open video device.")
    exit(1)

print("Successfully opened video device.")
cap.set(cv2.CAP_PROP_FRAME_WIDTH, frame_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height)
cap.set(cv2.CAP_PROP_FPS, frame_rate)

ofs = open("/dev/fb0", "wb")

while True:
    ret, frame = cap.read()
    if frame.dtype != np.uint8 or frame.ndim != 3:
        print("Not 8 bits per pixel and channel.")
    elif frame.shape[2] != 3:
        print("Not 3 channels.")
    else:
        frame = cv2.transpose(frame)
        frame = cv2.flip(frame, 0)
        frame = cv2.resize(frame, (DISPLAY_X, DISPLAY_Y))
        framebuffer_width = DISPLAY_X
        _ = open("/sys/class/graphics/fb0/bits_per_pixel", "r")
        framebuffer_depth = int(_.read()[:2])
        _.close()
        frame_size = frame.shape
        framebuffer_compat = np.zeros(frame_size, dtype=np.uint8)
        if framebuffer_depth == 16:
            framebuffer_compat = cv2.cvtColor(frame, cv2.COLOR_BGR2BGR565)
            for y in range(frame_size[0]):
                ofs.seek(y * framebuffer_width * 2)
                ofs.write(framebuffer_compat[y].tobytes())
        elif framebuffer_depth == 32:
            split_bgr = cv2.split(frame)
            split_bgr.append(np.full((frame_size[0], frame_size[1]), 255, dtype=np.uint8))
            framebuffer_compat = cv2.merge(split_bgr)
            for y in range(frame_size[0]):
                ofs.seek(y * framebuffer_width * 4)
                ofs.write(framebuffer_compat[y].tobytes())
        else:
            print("Unsupported depth of framebuffer.")

cap.release()
ofs.close()

システムのビルド

SDK環境を初期化します。

source build/envsetup.sh

ビルドを実行します。

mp -j32

エラーが出た場合は、詳細ログを出力しながら単スレッドで再試行します。

mp -j1 V=s

固件のフラッシュ

U-Bootの設定変更

デフォルトではSDC2(SD-NAND)を対象としていますが、TFカード(SDC0)も利用可能です。U-Bootの設定を変更する必要があります。

brandy/brandy-2.0/u-boot-2018/drivers/sunxi_flash/mmc/sdmmc.c の188行目を以下のように修正:

return sdmmc_init_for_sprite(0, 0); // 原:return sdmmc_init_for_sprite(0, 2);

変更後は固件を再ビルドし、空のTFカードを挿入してフラッシュします。

USBカメラの使用

USB UVCカメラを使用するには、以下の設定が必要です。

-> Device Drivers
   -> Multimedia support (MEDIA_SUPPORT [=y])
      -> Media USB Adapters (MEDIA_USB_SUPPORT [=y])
         -> USB Video Class (UVC) (USB_VIDEO_CLASS [=y])

ビルド後、USBをHOSTモードに切り替えます。

cat /sys/devices/platform/soc/usbc0/usb_host

接続成功後、camerademo コマンドでカメラテストが可能になります。

RTSPサーバーの構築

V4L2で画像を取得 → 硬体エンコーダーでH.264に変換 → RTSPサーバーにストリーム送信

提供された tinyvisionIpcV1 を使用して、ネットワークカメラとして動作させます。

adb push tinyvisionIpcV1 /root
chmod +x tinyvisionIpcV1
ifconfig eth0 192.168.2.17 broadcast 192.168.2.255 netmask 255.255.255.0 up
ifconfig lo 127.0.0.1 up
route add default gw 192.168.2.1
./tinyvisionIpcV1 640 480 30

外部から rtsp://192.168.2.17/live でストリームを受信可能です。

ffplay.exe -rtsp_transport tcp rtsp://192.168.2.17/live

独自ビルドを行う場合、CMakeを使用します。環境変数 STAGING_DIR を設定してください。

export STAGING_DIR="/root/tina-v853-open/out/v851se/tinyvision/openwrt/staging_dir/"
cmake .
make

タグ: linux OpenCV v4l2 RTSP TinyVision

8月16日 23:14 投稿