PaPeRo iのカメラでリアルタイムモニタ Golang編

 PaPeRo iのカメラでリアルタイムモニタ Python編と同じ事をGolangを使ってやってみました。構成や使用するAPI、カメラ等については同じなのでそちらをご参照ください。

Golangで共有メモリを操作する方法

 Golangではgolang.org/x/sys/unixパッケージを使用して共有メモリを操作する事ができます。

import (
    "log"
    "unsafe"
    "golang.org/x/sys/unix"
)

const IPC_CREAT = 00001000
const IPC_EXCL = 00002000
const IPC_RMID = 0

func shmget(siz int, shmflag int) (int, error) {
    shmid, _, err := unix.Syscall(unix.SYS_SHMGET, uintptr(0), uintptr(siz), uintptr(shmflag))
    if err != 0 {
        return 0, err
    }
    return int(shmid), nil
}

func shmdt(shmaddr unsafe.Pointer) error {
    _, _, err := unix.Syscall(unix.SYS_SHMDT, uintptr(shmaddr), uintptr(0), uintptr(0))
    if err != 0 {
        return err
    }
    return nil
}

func shmat(shmid int, addr unsafe.Pointer, flags int) (unsafe.Pointer, error) {
    res, _, err := unix.Syscall(unix.SYS_SHMAT, uintptr(shmid), uintptr(addr), uintptr(flags))
    if err != 0 {
        return nil, err
    }
    return unsafe.Pointer(res), nil
}

// 共有メモリの割り当て
func ShmCreate(siz int) (int, error) {
    res, err := shmget(siz, IPC_CREAT|IPC_EXCL|0600)
    return res, err
}

// 共有メモリのアタッチ
func ShmAttatch(shmid int) (unsafe.Pointer, error) {
    res, err := shmat(shmid, unsafe.Pointer(nil), 0)
    return res, err
}

// 共有メモリのデタッチ
func ShmDetatch(shmaddr unsafe.Pointer) error {
    err := shmdt(shmaddr)
    return err
}

// 共有メモリの破棄
func ShmDelete(shmid int) error {
    opt := 0
    _, _, err := unix.Syscall(unix.SYS_SHMCTL, uintptr(shmid), uintptr(IPC_RMID), uintptr(opt))
    if err != 0 {
        return err
    }
    return nil
}

Python版同様起動時に共有メモリの割り当てを行い、取得したIDをSendStartCapturing()に渡すことでカメラ画像が取得できるようになります。データは共有メモリのアタッチを行って取得したunsafe.Pointerから読み出します。unsafe.Pointerは固定長のbyte配列にキャストすることで通常の[]byteとして読み出せるようになります。

GolangでVGAカメラのデータをJPEGに変換する

 Pythonでは試しませんでしたがコンパイラ言語であるGolangは高速処理が期待できるので、VGAカメラデータのPaPeRo i上でのJPEG変換も試してみました。まずJavaScript版と以下のロジックでyuyv422からRGBAへ変換し、標準パッケージimage/jpegでJPEGに変換します。

func clipval(minv, maxv, val int) int {
    res := val
    if val < minv {
        res = minv
    }
    if val > maxv {
        res = maxv
    }
    return res
}

func yuyv2rgba(yuyv []byte, width, height int) *image.RGBA {
    od := image.NewRGBA(image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{width, height}})
    opos := 0
    for h := 0; h < height; h++ {
        for w := 0; w < width; w += 2 {
            ipos := 2 * (width*h + w)
            mc := 298 * (int(yuyv[ipos+0]) - 16)
            d := int(yuyv[ipos+1]) - 128
            e := int(yuyv[ipos+3]) - 128
            v0 := (mc + 409*e + 128) >> 8
            v1 := (mc - 100 + d - 208*e + 128) >> 8
            v2 := (mc + 516*d + 128) >> 8
            if 0 > v0 || v0 > 255 {
                v0 = clipval(0, 255, v0)
            }
            if 0 > v1 || v1 > 255 {
                v1 = clipval(0, 255, v1)
            }
            if 0 > v2 || v2 > 255 {
                v2 = clipval(0, 255, v2)
            }
            od.Pix[opos+0] = byte(v0)
            od.Pix[opos+1] = byte(v1)
            od.Pix[opos+2] = byte(v2)
            od.Pix[opos+3] = 255
            opos += 4

            mc = 298*int(yuyv[ipos+2]) - 16
            v0 = (mc + 409*e + 128) >> 8
            v1 = (mc - 100 + d - 208*e + 128) >> 8
            v2 = (mc + 516*d + 128) >> 8
            if 0 > v0 || v0 > 255 {
                v0 = clipval(0, 255, v0)
            }
            if 0 > v1 || v1 > 255 {
                v1 = clipval(0, 255, v1)
            }
            if 0 > v2 || v2 > 255 {
                v2 = clipval(0, 255, v2)
            }
            od.Pix[opos+0] = byte(v0)
            od.Pix[opos+1] = byte(v1)
            od.Pix[opos+2] = byte(v2)
            od.Pix[opos+3] = 255
            opos += 4
        }
    }
    return od
}

他にPython版同様ffmpegとブラウザでの変換も試してみました。
 ソースとPaPeRo i用ビルド済みバイナリはここにあります。local/paperocamera/paperocameraをパペロに転送して任意の場所で実行し、パペロのIP:8865/cameraをブラウザで開き、パペロの座布団向かって右ボタンを押すとカメラ画像が表示されます。
 なおカメラと変換の種類はmain.goの定数paperoCameraSize、convOnPapero、useFfmpegと、servers.goの290目付近のJavaScriptソースの変数orgSizeとCONV_ON_PAPEROを同時に変更する必要があります。また、カメラの種類を変更する場合にはパペロを一度再起動する必要がある様です。

実行結果

 実行結果は以下の様になりました。PCはCore i7の比較的高性能なWindows10のPC、スマホは古いNexus4を使用しました。PC欄とスマホ欄は表示レート/表示遅延です。

Golang版の結果

No カメラ データ変換 1画像の転送データサイズ 挿入スリープ時間 パペロCPU使用率 PC スマホ 備考
1 VGA ffmpeg 約40kB? 1msec 約40% 約4fps/0.6sec 約4fps/0.7sec
2 VGA ブラウザ 614.4kB 100msec 約12% 約7fps/0.4sec ×(遅延大) これ以上スリープ減らすと遅延による廃棄発生
3 VGA ブラウザ 614.4kB 250msec 約10% 約3fps/0.4sec 約3fps/0.7sec スマホは数秒停止することあり
4 SXGA なし 200kB弱~300kB強? 50msec 約15% 約15fps/0.3sec ×(数秒でエラー停止)
5 VGA Golang 約40kB? 1msec 約50% 約4fps/0.6sec 約4fps/0.7sec

 傾向はPython版と似ており結論も変わりませんが、表示性能はPythonより少し良い程度、CPU使用率はffmpegを除き半減となりました。No5のGolangによる変換はCPU負荷が高く、ffmpegの方が良い結果となっています。
 比較のためPython版の結果も再掲します。

Python版の結果(再掲)

No カメラ データ変換 1画像の転送データサイズ 挿入スリープ時間 パペロCPU使用率 PC スマホ 備考
1 VGA ffmpeg 約40kB? 1msec 約40% 約3fps/0.6sec 約3fps/0.8sec
2 VGA ブラウザ 614.4kB 100msec 約24% 約5fps/0.5sec ×(遅延大) これ以上スリープ減らすと遅延による廃棄発生
3 VGA ブラウザ 614.4kB 250msec 約20% 約3fps/0.5sec 約3fps/0.8sec スマホは数秒停止することあり
4 SXGA なし 200kB弱~300kB強? 30msec 約35% 約12fps/0.3sec ×(数秒でエラー停止) スリープ0.5secでもスマホNG

0