月別アーカイブ: 2015年12月

TensorFlowでのjpeg読み込みについて(Qiita)

Qiitaに記事を書きました。TensorFlow自身が持つjpegデコーダを使いたかったのですが、あまり実例を見かけなかったのと、「説明があまりない」という趣旨のことが書かれた記事があったので、理解できたことを形として残すことにしました。

同じものをこちらに掲載することも考えたのですが、とりあえず記事そのものは個人的にバックアップを保持しておくだけにとどめておきました。

ウェブサービスへの依存については難しいところです。そう思いつつたくさんのサービスを既に使ってしまっているのですが…ある程度は最悪の事態に備えておきたいところです。

Orange PI PCで動画のハードウェアエンコードはできるのか

これはLinux Advent Calendar 2015 18日目の記事です。

ARMベースの小型PCとしてRaspberry PIシリーズが人気です。ハードウェアによる動画(H.264)エンコード支援がついていて、それが目的で使っている人もいるようです。

しかし私はRaspberry PI 2の代わりにOrange PI PCを購入しました。機能的には持っているようなので、できればなんとか使いたいところです。

参考にするため、そもそもRasPiではどうやっているのかをちょっと調べてみました。Googleで検索すると、gstreamerでomxh264encを使うという方法が紹介されています

これはgst-omxというコンポーネントのようで、OpenMaxというコーデックの取り扱いを抽象化したAPIのようです。さらに調べてみると、RasPiではlibopenmaxil.soという共有ライブラリが呼び出されているようです。

ではこいつのソースコードはどこだと探してみましたが、ファームウェアの一部という扱いでバイナリーのみが配布されているという残念な状況でした。RasPi側からの調査はここが限界です。

ではOrange PIではどうか。Orange PIとAllwinnerのチップの情報に関しては、linux-sunxi.orgが総本山のようです。ここにいろいろと情報がありました。

Orange PI PC用カーネルにはcedar-veというモジュールが追加されており、これがハードウェアコーデック用のデバイスドライバとなっています。/dev/cedar_devというスペシャルデバイスを介してioctlで直接ハードウェアを操作する機能を提供しています。残念ながらメインラインにはマージされていないようです。

そしてそれを使うよう改造されたffmpegが公開されています。これを動かすことを目標としていろいろ試してみました。リポジトリにはdeb化されたバイナリーもありますが、どうもwheezy向けのようです。しかもパッケージングの作法もよろしくなく、既存のdebianパッケージとファイル単位でいろいろ衝突を起こすので、これを利用するのはお勧めしません。

そこで手動でのビルドを行いましたが、ライブラリ周りで苦労しました。最終的には以下のような手順でビルドできると思います。

# apt-get build-dep ffmpeg
# apt-get install frei0r-plugins-dev libgnutls28-dev ladspa-sdk \
libiec61883-dev libavc1394-dev libass-dev libbluray-dev libbs2b-dev  \
libcaca-dev flite1-dev libgme-dev libgsm1-dev libmodplug-dev \
libmp3lame-dev libopencore-amrnb-dev libopencore-amrwb-dev \
libopenjpeg-dev libopus-dev librtmp-dev libshine-dev libssh-dev \
libspeex-dev libtheora-dev libtwolame-dev libvo-aacenc-dev libvo-amrwbenc-dev \
libvorbis-dev libvpx-dev libwavpack-dev libwebp-dev libx265-dev \
libxvidcore-dev libzvbi-dev libopenal-dev
# ./configure --prefix=/opt/ffmpeg \
        --build-suffix="-ffmpeg" \
        --toolchain=hardened \
        --enable-gpl \
        --enable-shared \
        --disable-stripping \
        --disable-decoder=libopenjpeg \
        --disable-decoder=libschroedinger \
        --enable-avresample \
        --enable-avisynth \
        --enable-gnutls \
        --enable-ladspa \
        --enable-libass \
        --enable-libbluray \
        --enable-libbs2b \
        --enable-libcaca \
        --disable-libcdio \
        --enable-libflite \
        --enable-libfontconfig \
        --enable-libfreetype \
        --enable-libfribidi \
        --enable-libgme \
        --enable-libgsm \
        --enable-libmodplug \
        --enable-libmp3lame \
        --enable-libopenjpeg \
        --enable-openal \
        --enable-libopus \
        --enable-libpulse \
        --enable-librtmp \
        --enable-libschroedinger \
        --enable-libshine \
        --enable-libspeex \
        --enable-libssh \
        --enable-libtheora \
        --enable-libtwolame \
        --enable-libvorbis \
        --enable-libvpx \
        --enable-libwavpack \
        --enable-libwebp \
        --enable-libx265 \
        --enable-libxvid \
        --enable-libzvbi \
        --disable-opengl \
        --enable-x11grab \
        --enable-version3 \
        --enable-libopencore_amrnb \
        --enable-libopencore_amrwb \
        --enable-libvo_aacenc \
        --enable-libiec61883 \
        --enable-libzmq \
        --enable-frei0r \
        --enable-libx264 \
        --enable-libopencv \
        --enable-libvo_amrwbenc
# make

ビルドしたバイナリを実行してみます。/dev/cedar_devへのrwアクセスができる状態でffmpegを実行します。

$ ffmpeg -i inputfile.ts -vcodec cedrus264 -pix_fmt nv12 output.mp4
ffmpeg version git-2015-01-22-f86a076 Copyright (c) 2000-2014 the FFmpeg developers
  built on Dec  7 2015 14:04:23 with gcc 4.9.2 (Debian 4.9.2-10)
(中略)
[VE SUNXI] VE version 0x0000 opened.
[cedrus264 @ 0x1f42f0] Cannot allocate frame.
Output #0, mp4, to 'output.mp4':
    Stream #0:0: Video: h264, q=2-31, 128 kb/s, SAR 1:1 DAR 0:0, 29.97 fps
    Metadata:
      encoder         : Lavc56.0.101 cedrus264
    Stream #0:1: Audio: aac, 0 channels, 128 kb/s
    Metadata:
      encoder         : Lavc56.0.101 libvo_aacenc
Stream mapping:
  Stream #0:0 -> #0:0 (mpeg2video (native) -> h264 (cedrus264))
  Stream #0:1 -> #0:1 (aac (native) -> aac (libvo_aacenc))
Error while opening encoder for output stream #0:0 - maybe incorrect parameters such as bit_rate, rate, width or height

残念ながらうまく動きませんでした。gdbで追いかけてみると、ve_open()で/dev/cedar_devをオープンしてioctl IOCTL_GET_ENV_INFOで構造体ve_infoの値を取得していますが、これ自体は失敗しないものの、各種メンバの値reserved_mem, reserved_mem_size, registersがすべて0になっています。これによりve_mallocが失敗して6行目のエラーメッセージが表示されます。ドライバ側のコードを読むと、「このインターフェースは使うな」というコメントがあり、実際に0を返していました。きっと古いcedar_devで動くコードだったのでしょう…

http://linux-sunxi.org/CedarX/Encoderの記述によると、現在あるコード以外にもsunxi_memドライバという物理メモリにアクセスするドライバ(/dev/sunxi_mem)と併用して動くCedarXというライブラリが別にあるようです。ハードウェアエンコーダのサンプルコードもあるのですが、やはりバイナリでしか存在しないライブラリ群に依存しています。

それでもいいからなんとか動かせないかと、sunxi_memドライバを追加した勝手カーネルをビルドしようとしたのですが、mach/includes.hという存在しないヘッダファイルを必要としてビルドできませんでした。ここはまだ調査不足です。

現状の結論として、Orange PI PCでH.264のハードウェアエンコードは「できそうな雰囲気」という煮え切れない結論となってしまいました。linux-sunxi MLの購読を始めたので、今後はこのあたりどうなっているかを尋ねてみたいと思っています。

Orange PI PC

g新部さんが強くプッシュしているOrange PI PCを購入しました。実際に購入したのは先月ぐらいだったのですが、最近ようやくまともに使いだしたところです。

Orange PIシリーズは複数あるRaspberry PIのフォロワーの一つです。私は以前秋月電子で同じような”Banana PI”というのを見かけたことがあるのですが、気が付くと手に入らなくなっていました。

g新部さんが最も良しとしている点は、ハードウェアメーカー(Allwinner)が情報開示に積極的にであることだそうです。Raspberry PIはその点で確かに厳しいようです。

そのあたりの話が11月のFSIJ月例会でありました。その時の発表内容をTogetterでまとめたので、興味のある方はご覧ください。月例会のページにもいくつか情報があります。

私の興味はH.264のハードウェアエンコーディングにあります。g新部さんからお勧めされた時に、Allwinner H3のスペック的にハードウェアエンコーダーを持っていることを確認して購入しました。しかしまだそこを動かすには至っていません。情報はそこそこあるようなので、なんとかしたいところです。

機械学習/TensorFlow初心者の雑感

いろいろあって、不慣れながらTensorFlowを使ってみたのですがいまいちわかってなくていろいろはまったので、記録に残しておこうと思います。

画像認識タスク

チュートリアルには手書き数字のデータセットThe MNIST Dataを機械学習で認識できるモデルを作る、というものがあります。画像認識の世界では非常にポピュラーなものだそうです。

これ自体はチュートリアルの通りに実行すればごく普通に動きます。ナイーブな実装でも90%程度の認識率のモデルができます。これを、任意の別の画像に応用させようと考えました。対象はカラー画像で、単純に1種類の物体の区別をさせようとしました。

TensorFlowには画像を読み込む機能が用意されています。tf.image.decode_jpegでjpeg形式を読み込んで、横x縦x3(RGB)のint32型3次元行列を返します。さらに、これをリサイズする関数tf.image.resize_imagesもあります。指定した縦横のサイズにリサイズされ、値もfloat型に正規化されます。

ではこれを単純にロジスティック回帰にいれようと、tf.matmulで行列の積を求めようとしたらtf.matmulでエラーになりました。コードは以下のような感じです。

import tensorflow as tf
mat_size = 32 # 32x32ピクセル
mat_ch = 3 # RGB
input_x = tf.placeholder("float", [None, mat_size, mat_size, mat_ch])
weight = tf.Variable(tf.zeros([mat_size, mat_size, mat_ch]))
bias = tf.Variable(tf.zeros([1])
>y = tf.nn.softmax(tf.matmul(input_x, weight) + bias)

いろいろ試した挙句、この関数は入力が2次元行列以外を受け付けないことがわかりました。よくよく見るとドキュメントにもそう書かれていました。

他の人はどうしているか探してみると、画像をOpenCVで読み込ませてなおかつ単純に平坦化(flatten)した1次元行列として扱っていました。結局自分もそれに習って実装しなおしました。その結果、動くコードは以下のように修正されました。

import tensorflow as tf
mat_size = 32 # 32x32ピクセル
mat_ch = 3 # RGB
input_x = tf.placeholder("float", [None, mat_size * mat_size * mat_ch])
weight = tf.Variable(tf.zeros([mat_size * mat_size * mat_ch]))
bias = tf.Variable(tf.zeros([1])
y = tf.nn.softmax(tf.matmul(input_x, weight) + bias)

扱うデータが1次元なのにinput_xが2次元なのは、input_xが受け付けるデータは訓練データの配列だからです。ミニバッチ処理のためにそのような作りになっているようです。訓練データが1つだけの配列にすればもちろん1つのデータに対しても動きます。

一応、tf.reshape(vec, [-1])とすれば平坦化できるようなのですが、今のところ確かめていません。

セッションと値の評価

とりあえず自分で用意したデータを使って訓練をさせてみたのですが、思うような結果が得られません。というか全然学習している気配がありません。

結論から言えば、単純に訓練データが圧倒的に不足していたようなのですが、そもそも学習できていないということを把握するのにちょっと苦労しました。

個々の重みはweightに入っているはずなのですが、この値の中をどうやって参照するかではまりました。単純にprint(weight)などとしても__str__によって変換された結果(オブジェクトのクラス名等)しか出てきません。

正しく値を取得するには、TensorFlowのセッション上での値の評価が必要です。

sess = tf.Session()
print(sess.run(weight))

# もしくは
# sess.as_default()
# print(weight.eval())

この結果、すべての値が0.0のままだったので「これ、学習できてない!」とようやく理解できました。TensorFlowとの格闘はまだまだつづきそうです。

参考