<!--親の顔より見た光景-->

日々の発見を残していきます。

PICO-PI-IMX8 環境構築メモ

PICO-PI 環境構築メモ

目的:
 組み込みLinux Driver 開発勉強のため、PICO-PI-IMX8 環境構築を行う。
 環境構築後、driver 改造、新規 driver 作成が可能となる。
基本環境:
 Windows 上で動く VirtualBox(Ubuntu16.04) を host OS とする
必要なもの
 Ether ケーブル
 PICO-PI-IMX8

virtual box 導入

参考:https://qiita.com/ykawakami/items/4bae371932110b2e25e3
割当メモリ:2048MB
ハードディスク容量 120GB
以降の作業はVirtual Box 上での作業

i.MX8M ARM Toolchain DL

gcc-linaro-6.4.1-2017.11-x86_64_aarch64-linux-gnu.tar.xz を DL
参考:https://www.technexion.com/support/knowledgebase/preparing-a-toolchain-for-building-arm-binaries-on-x86-hosts/

DL後、
sudo tar xvf gcc-linaro-6.4.1-2017.11-x86_64_aarch64-linux-gnu.tar.xz -C /opt/

toolchain 環境変数設定

以下を ~/.bashrc に追記、source ~/.bashrc を実行

export ARCH=arm64
export CROSS_COMPILE=/opt/[toolchain directory]/bin/aarch64-linux-gnu-
export CC=/opt/[toolchain directory]/bin/aarch64-linux-gnu-

hello world 作成

hello world を行う test.cを作成
$ {CC}gcc test.c -o test
$ file test
ARM64 形式であることを確認

kernel source 取得

任意ディレクトリで
$ git clone https://github.com/TechNexion/linux.git

kernel build

$ cd ~/path/to/work/linux
$ make defconfig
$ make all

drivers/media/platform/mxc/capture/mx6s_capture.c
probe() 先頭に pr_info("20200831 test"); を追記
PICO-PI 起動時に以下の通りログが現れるため、ログを保存、検索をかける

参考:PICO-PI 起動時のログ
[ 1.787511] input: 30370000.snvs:snvs-powerkey as /devices/platform/30370000.snvs/30370000.snvs:snvs-powerkey/input/input0
[ 1.801515] snvs_rtc 30370000.snvs:snvs-rtc-lp: rtc core: registered 30370000.snvs:snvs- as rtc0
[ 1.811097] i2c /dev entries driver
[ 1.817608] 20200831 test
[ 1.820700] 20200831 test
[ 1.825344] mxc-mipi-csi2_yav 30a70000.mipi_csi1: mipi_csi2_probe
[ 1.831571] CSI: Registered sensor subdevice: mxc-mipi-csi2.0
[ 1.837429] mxc-mipi-csi2_yav 30a70000.mipi_csi: Remote device a

rootfs 取得

Ubuntu 20.04 file system を取得する。
参考:https://www.digikey.com/eewiki/display/linuxonarm/MCIMX8M-EVK#MCIMX8M-EVK-Ubuntu20.04LTS

以下を実行。
$ wget -c https://rcn-ee.com/rootfs/eewiki/minfs/ubuntu-20.04-minimal-armhf-2020-05-10.tar.xz

取得した tar.xz を /public/nfsroot で展開

$ ls /public/nfsroot
bin dev home media opt root sbin sys usr
boot etc lib mnt proc run srv tmp var

nfs server 構築

参考:https://qiita.com/salty-vanilla/items/5bc95ad47af6bf051bf7

$ sudo apt-get install nfs-kernel-server
/etc/exports に以下を追記
/public/nfsroot/ 192.168.4.200(rw,sync,no_subtree_check)

tftp server 構築

$ sudo apt install tftpd-hpa tftp-hpa
$ sudo systemctl enable tftpd-hpa
$ sudo systemctl restart tftpd-hpa
$ mkdir /tftpboot

/etc/default/tftpd-hpa に以下を追記
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--ipv4 --secure --create"

tftpserver を restart
$ sudo service tftpd-hpa restart

起動Image の配置

kernel build 成果物である Image, dtb を tftpserver に配置する
$ cd ~/path/to/work/linux
$ sudo cp arch/arm64/boot/Image /tftpboot
$ sudo cp arch/arm64/boot/dts/freescale/pico-8m.dtb /tftpboot

teraterm install

teratermWindows へ install する
windowsteraterm を install

PICO-PI-IMX8 起動

typeB USB, USB type C をPC と接続。type C 接続でボード電源が入る。
teraterm を起動、Serial 接続で port 3,4 (環境による)いずれかを選択してボードのリセットボタンを押下
起動ログが流れる port で、ボーレートを115200 に設定。再起動。
文字化けのない起動ログが現れたら成功。
User name: root
password: root
でログイン

工場出荷状態では eMMC boot 設定となっているため、uboot 環境変数を編集して NFS boot に切り替える必要アリ

virtual box ネットワーク設定

Virtual Box を終了
Virtual Box 起動画面 -> 設定
設定 -> ネットワーク ->
割り当て:ブリッジアダプター
名前:Realtek PCIe FE Familiy Controller
とする
名前の部分は環境によって変えること。今回はEther ポートに直接接続。

Ubuntu 起動
Ubuntu 側で network アドレスを 192.168.4.10 に設定

PICO-PI を起動、teraterm 上で ifconfig eth0 192.168.4.200 を実行
Ubuntu から ping 192.168.4.200 が通ればOK

uboot 環境変数に Ether boot 設定を追加

デフォルトで NFS boot が走るように uboot 環境変数を変更する
変更前に printenv で工場出荷状態の環境変数を確認、保存しておくこと

環境変数のバックアップ後、以下を実行。

setenv ipaddr 192.168.4.200
setenv serverip 192.168.4.10
setenv nfsroot /public/nfsroot
setenv fdt_addr 0x43000000
setenv loadaddr 0x40480000
setenv fdt_file pico-8m.dtb
setenv bootcmd 'run netboot'

setenv netargs 'setenv bootargs root=/dev/nfs rw ip=${ipaddr}:${serverip}:${serverip}:255.255.255.0:hostname::off nfsroot=${serverip}:${nfsroot},nolock,vers=4,tcp'

ログイン

PICO-PI 電源ON, ログイン画面で
username: ubuntu
pass: temppwd

ログインできれば成功。

ステレオカメラとIRステレオカメラによるヘビの運動量算出【4】

通常ステレオカメラとIRステレオカメラを電気スタンドに固定する。

1. プレートにカメラを固定
1. 土台に穴を空ける

f:id:akagi13213:20190331190500p:plain
図1 カメラの土台に穴をあける

2. ステレオカメラを固定する

f:id:akagi13213:20190331190626p:plain
図2 カメラを固定

3. マイコンを固定する

f:id:akagi13213:20190331190902p:plain
図3 マイコンを固定

4. 電気スタンドに固定
電気スタンドのソケット以降を外し、プレートを固定する。

f:id:akagi13213:20190331191018p:plainf:id:akagi13213:20190331192433p:plain
図4 プレートを電気スタンドに固定

ステレオカメラとIRステレオカメラによるヘビの運動量算出【3】

2つのIR カメラをrasberry pi に繋ぐ。

1. 準備

  • rasberry pi用マルチカメラアダプタ

f:id:akagi13213:20190321224936p:plain
図1 rasberry pi 用マルチカメラアダプタ

  • IR カメラ x 2

f:id:akagi13213:20190318002506p:plain
図2 赤外線カメラ

2. 接続

写真のようにフレキケーブルを接続する。GPIOピンの位置はこの通りにしないと壊れると思う。

f:id:akagi13213:20190321225615p:plainf:id:akagi13213:20190321225737p:plainf:id:akagi13213:20190321225812p:plain


マルチカメラアダプタはABCDの4つ口があり、最大4段まで拡張が可能で、1段積み用の設定、2段積み用・・と設定がある。
1段しか使わない場合は、DIPスイッチの1, 5のみONにする。

f:id:akagi13213:20190322001729p:plain
図3 1段積み用のDIPスイッチ

3. 動作確認

このマルチカメラアダプタにはライブラリはない(たぶん)ので泥臭くコードを書く必要がある。

1. コード

#!/bin/python

import RPi.GPIO as gp
import os
import time

gp.setwarnings(False)
gp.setmode(gp.BOARD)

# Setup the stack layer 1 board
gp.setup(7, gp.OUT)
gp.setup(11, gp.OUT)
gp.setup(12, gp.OUT)

def main():
  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)
  capture(1)
  gp.output(7, False)
  gp.output(11, True)
  gp.output(12, False)
  capture(3)

def capture(cam):
  cmd = "raspistill -o capture_%d.jpg" % cam
  os.system(cmd)

if __name__ == "__main__":
  time.sleep(5)
  main()
  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)

●コードの解説

  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)
  capture(1)
  gp.output(7, False)
  gp.output(11, True)
  gp.output(12, False)
  capture(3)

CAM A をキャプチャする場合、ピン出力を以下にする。

ピン番号
7 OFF
11 OFF
12 ON

CAM C をキャプチャする場合、ピン出力を以下にする。

ピン番号
7 OFF
11 ON
12 OFF

その他、マルチカメラアダプタの詳細設定は以下を参照。
http://www.arducam.com/downloads/RaspCAM/RaspberryPi_Multi_Camera_Adapter_Module_UG.pdf


2. 赤外線ライトの強度調整
添付画像の赤枠箇所をマイナスドライバーで回すと、赤外線ライトの強度を変更できる。

f:id:akagi13213:20190322002634p:plain
図4 可変抵抗でライト強度を調整

3. カメラピント調整
図の赤枠箇所を回すと、ピント調整できる。

f:id:akagi13213:20190322002813p:plain
図5 ピント調整

4. 結果
左右でこんな画が撮れた。

f:id:akagi13213:20190322002942p:plainf:id:akagi13213:20190322003012p:plain
図6 IRカメラ画

ステレオカメラとIRステレオカメラによるヘビの運動量算出【2】

ステレオIR カメラを作成する。

1. 準備

  • 木材

f:id:akagi13213:20190322200336p:plain
図1 木材

  • IR カメラ x 2

f:id:akagi13213:20190318002506p:plain
図2 赤外線カメラ

  • 電動ドリル

f:id:akagi13213:20190322200847p:plain
図3 電動ドリル

  • ノコギリ

f:id:akagi13213:20190322201007p:plain
図4 ノコギリ

2. 作成

電動ドリルでネジ穴を空ける。

f:id:akagi13213:20190318002402p:plain
図5 ネジ穴をあける

カメラを固定する

f:id:akagi13213:20190318003400p:plain
図6 カメラを固定


余分な部分をノコギリで切って、やすりで角を削ったら完成。

f:id:akagi13213:20190318003455p:plain
図7 完成

ステレオカメラとIRステレオカメラによるヘビの運動量算出【1】

過去に挑戦した、ペットのヘビを監視するシステムを進化させることにした。
参考:ヘビの安否確認システム - <!--親の顔より見た光景-->

前回のシステムは、通常のUSBカメラを用いた撮影なので
夜間の行動を見ることができなかった(夜行性なのに。

そこで今回は、IRカメラを導入 & 複眼カメラにして1日の運動量を計測することにした。

今回は準備編。

---------------------------

●背景
ペットのヘビを監視するシステムを構築し、1日あたりの運動量を測りたい。

●原理
通常のステレオカメラとIRステレオカメラを作成し、被写体の深度を測る。
昼間は通常のカメラ、夜間はIRカメラを使用する。
深度画像と物体検出アルゴリズムを用いることで、ヘビの運動量を計算することが可能なはず。

こんなイメージで観察したい。

f:id:akagi13213:20190317191817p:plain
図1 作りたいもののイメージ

●実験準備
ざっとコレだけ必要

f:id:akagi13213:20190317192507j:plain
図2 システム構築のために用意したもの

マイコン周り

モノ 値段 参考リンク
Rasberry pi 3 B+ ¥5100 Amazon
SDカード 32GB ¥1350 Amazon
ACアダプタ 3.0A ¥1580 Amazon
通常カメラ(CSI インターフェース) ¥1699 Amazon
赤外線カメラ(CSI インターフェース) ¥2899 Amazon
Raspberry Pi用マルチカメラアダプタ(CSI 拡張基盤) ¥6171 スイッチサイエンス


●機構周り(ホームセンターで全て購入)

モノ 値段 参考リンク
アームライト(電球別売り) ¥2894 Amazon
充電式電動ドリルドライバー ¥3218 ビバホームオンラインショップ
精密ドライバー ¥321 ホームセンター
カメラプレート用木材 ¥116 ホームセンター
マイコン土台用木材 ¥354 ホームセンター
のこぎり ¥1002 ホームセンター
紙やすり #240 ¥84 ホームセンター
M3 スペーサ * 4 ¥578 ホームセンター
M3 ねじ+ナット+ワッシャー ¥159 ホームセンター
M2 ねじ+ナット+ワッシャー ¥159 ホームセンター
定規 ¥0 どこでも

電動ドリルが意外と安かったので、
カメラのプレートと、マイコン用の土台を木材で作ることにした。
あと、のこぎりがカッコいい

f:id:akagi13213:20190317200459j:plain
図3 のこぎり

●ソフトウェア

モノ 値段 参考リンク
fusion 360(CAD ソフト)無償体験版 ¥0 ホームページ

図4のような感じで設計し、IR ステレオカメラの完成イメージを掴む。
(手間はかかるが、カメラ位置微調整用のネジ穴数を増やしたほうが良いと気づけた。)

f:id:akagi13213:20190317201542p:plain
図4 CAD ソフトでIRカメラを設計

●合計
¥27684
(めっちゃかかった……)

【C++】離散フーリエ級数展開

理解するためにすべて手書きで書いた。
乱数で作ったグラフを、
三角関数の足し合わせのみでキレイに再現できている。
f:id:akagi13213:20190203015531p:plain

(あと一息で離散フーリエ変換ができそう)

#include "pch.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

#define M_PI    3.14159265358979323846

int main(void)
{
    std::ifstream ifs( "./source.csv" );
    if ( ifs.fail() ) {
        std::cout << "file read error..." << std::endl;
        return -1;
    }

    std::string str;
    std::vector<double> f;
    while ( getline( ifs, str ) ) {
        f.push_back(std::stod( str ));
    }

    double a0 = 0;
    std::vector<double> an;
    std::vector<double> bn;

    double sum = 0;
    int N = f.size();
    for ( int x = 0; x < N; x++ ) {
        sum += f[x];
    }
    a0 = sum / N;

    for ( int n = 1; n < N; n++ ) {
        double a_sum = 0;
        double b_sum = 0;
        
        for ( int x = 0; x < N; x++ ) {
            a_sum += f[x] * cos( 2 * M_PI * n * x  / N );
            b_sum += f[x] * sin( 2 * M_PI * n * x / N );
        }
        
        if ( n == ( N / 2 - 1 ) ) {
            an.push_back( a_sum / N );
            bn.push_back( b_sum / N );
        }
        else {
            an.push_back( 2 * a_sum / N );
            bn.push_back( 2 * b_sum / N );
        }
    }
    
    std::vector<double> fx_fourier;
    for (int x = 0; x < N; x++) {
        double a_sum = 0;
        double b_sum = 0;
        for ( int n = 1; n < N; n++ ) {
            a_sum += an[n - 1] * cos( 2 * M_PI * n * x / N );
            b_sum += bn[n - 1] * sin( 2 * M_PI * n * x / N );
        }
        fx_fourier.push_back( a0 + a_sum + b_sum );
    }

    std::ofstream dist;
    dist.open("dist.csv", std::ios::out);
    for ( int i = 0; i < fx_fourier.size(); i++ ) {
        dist << fx_fourier[i] << std::endl;
    }
    
    return 0;
}

【画像処理】2次微分フィルタ

二次微分フィルタを自作した。

def edge_filter_2(file):
    image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
    (height, width) = image.shape[:2]

    edge_width = image.copy()
    edge_height = image.copy()
    edge_lap = image.copy()
    for i in range(1, height-1):
        for j in range(1, width-1):
            left = image[i, j-1]
            right = image[i, j+1]
            top = image[i-1, j]
            bottom = image[i+1, j]
            center = -image[i, j] * 2

            edge_width[i, j] = abs(left + right + center)
            edge_height[i, j] = abs(top + bottom + center)
            edge_lap[i, j] = abs(top + bottom + left + right + center*2)
 
    cv2.imwrite('gray.png', image)
    cv2.imwrite('edge2_width.png', edge_width)
    cv2.imwrite('edge2_height.png', edge_height)
    cv2.imwrite('edge2_lap.png', edge_lap)

参考は以下。
画像処理フィルタの1次微分と2次微分の違い | ぱーくん plus idea

適用フィルタは以下。
横方向

0 0 0
1 -2 1
0 0 0

縦方向

0 1 0
0 -2 0
0 1 0

4近傍

0 1 0
1 -4 1
0 1 0

●横方向二次微分
f:id:akagi13213:20190120184119p:plain

●縦方向二次微分
f:id:akagi13213:20190120184130p:plain

●4近傍(ラプラシアンフィルタ)
f:id:akagi13213:20190120184140p:plain

★なぜ1次微分より2次微分のほうがエッジが目立つ?
f:id:akagi13213:20190120184749p:plain
左から、元画像・1次微分(横方向)・2次微分(横方向)

横方向微分の1次微分と2次微分の出力差を見てみる。
仮に、顔の輪郭にフィルタを適用するとして、
フィルタ適用前画素列を、以下とする

32 32 64 64 64 64 255 255

★★1次微分(横方向)

-1/2 0 1/2

このフィルタを適用すると(左端・右端は元画像のままとする)、

32 32 64 64 64 64 255 255

 ↓↓ 1次微分適用

32 16 16 0 0 95 95 255

★★2次微分(横方向)

1 -2 1

このフィルタを適用すると(左端・右端は元画像のままとする)、
※絶対値を画素値として採用

32 32 64 64 64 64 255 255

 ↓↓ 2次微分適用

32 32 -32 0 0 191 -191 255

1次微分より白成分が2倍になっている。