開発環境の準備
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
使用しているシェルが dash、zsh などの場合、対応する設定ファイルに該当する環境変数を定義してください。
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
同様に v851s や v853 でも同様の操作が可能です。その後、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 = <®_dcdc1>;*/
/*emmc io vol 3.3v*/
/*vqmmc-supply = <®_aldo1>;*/
/*emmc io vol 1.8v*/
/*vqmmc-supply = <®_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