はじめに
NVIDIA Jetson TX1 にWebカメラを接続すれば,カメラに映ったものをリアルタイムに物体認識するようなプログラムを作ることができます.JetsonはGPUを積んでいるので Deep Learning の推論もスムーズに動きます.この記事では,JetsonにSONYのデジタルミラーレス一眼を接続するための方法を説明します.Python/OpenCV/Caffeで実装します.
準備
ハードウェア,ライブラリ等,色々準備していきます.
SONY Camera Remote API
SONY製のデジタルカメラは外部からカメラを制御できるAPIを解放しています.上記WebサイトでAndroid版のSDKが配布されています.ただし,この記事ではLinuxからPythonでカメラと接続するので上記SDKは不要です.最近のSONY製デジタルカメラはだいたい対応しているようですが,本記事のコードはSONY ILCE-QX1(以下,QX1)で動作確認しています1.
ハードウェア
ソフトウェア
Jetson TX1(以下,TX1)にJetPackでインストールしておきます.
- OpenCV4Tegra
- Caffe
- Python 2.7
インストール作業はこちらを参考にしました.
Pythonライブラリ
TX1 の Python 2.7 環境にインストールしておきます.
SonyPy修正
SonyPyはQX1には非対応のため下記の通り修正が必要です.
sonypy/discovery.py
dd_regex = ('<av:X_ScalarWebAPI_Service>'
'\s*'
'<av:X_ScalarWebAPI_ServiceType>'
'([^<]+)'
'</av:X_ScalarWebAPI_ServiceType>'
'<av:X_ScalarWebAPI_ActionList_URL>'
'([^<]+)'
'</av:X_ScalarWebAPI_ActionList_URL>'
'\s*'
'<av:X_ScalarWebAPI_AccessType/>'
'</av:X_ScalarWebAPI_Service>')
def _parse_ssdp_response(self, data):
lines = data.split('\n')
if 'HTTP/1.1 200 OK' in lines[0]:
print 'OK'
else:
print 'NG'
headers = {}
for line in lines[1:]:
kv = line.split(': ', 1)
key = kv[0]
if len(kv) >= 2:
val = kv[1]
else:
val = ''
headers[key.lower()] = val
return headers
def _parse_device_definition(self, doc):
"""
Parse the XML device definition file.
"""
services = {}
for m in re.findall(dd_regex, doc):
service_name = m[0]
endpoint = m[1]
services[service_name] = endpoint
return services
def _read_device_definition(self, url):
"""
Fetch and parse the device definition, and extract the URL endpoint for
the camera API service.
"""
r = requests.get(url)
rt = r.text.strip().replace(" ", "").replace("\t", "").replace("\r", "").replace("\n", "")
services = self._parse_device_definition(rt)
return services['camera']
sonypy/camera.py
def _do_request(self, method, service, *args):
body = dict(method=method,
params=args,
id=1,
version=self.version)
data = json.dumps(body)
r = requests.post(self.endpoint + '/' + service, data=data)
resp = json.loads(r.text)
return resp['result']
def _decode_common_header(self, buf):
start, ptype, seq, timestamp = struct.unpack('>BBHI', buf)
return start, ptype, seq, timestamp
def _decode_payload_header(self, ptype, buf):
format = '>IBBBBIB'
buf = buf[:struct.calcsize(format)]
jpeg_size = struct.unpack('I', bytearray([buf[6], buf[5], buf[4], 0x00]))[0]
padding_size = struct.unpack('I', bytearray([buf[7], 0x00, 0x00, 0x00]))[0]
return jpeg_size, padding_size
def stream_liveview(self, url):
"""
Connect to a liveview-format URL and yield a series of JPEG frames.
"""
r = requests.get(url, stream=True)
while True:
# Read common header, 8 bytes.
start, ptype, seq, timestamp = self._decode_common_header(r.raw.read(8))
if start != 255:
continue
if ptype == 18:
# skip
r.raw.read(4+3+1+2+118+4+4+24)
elif ptype == 1 or ptype == 17:
# Read payload header, 128 bytes.
jpeg_size, padding_size = \
self._decode_payload_header(ptype, r.raw.read(128))
# Read JPEG frame.
jpeg_frame = r.raw.read(jpeg_size)
# Throw away the padding.
r.raw.read(padding_size)
yield [jpeg_frame, seq, timestamp]
_do_request関数の修正にともない,呼び出し箇所も適宜修正する必要があります.呼び出し箇所は camera.py ファイル内部に複数存在するので,それぞれ引数に'camera'を追加します.例えば,下記の通り.
result = self._do_request('actTakePicture', 'camera')
画像の取得から処理まで
QX1から画像を取得して,OpenCVでの画像処理やCaffeによる推論を実行するPythonプログラムを説明します.
準備
- QX1のWi-Fiアクセスポイントを立ち上げる
- TX1から上記のアクセスポイントにWi-Fi接続する
ライブラリ読み込み
from sonypy import Discoverer, Camera
import cv2
import numpy
import skimage
import caffe
QX1との接続確立
修正したSonyPyを使って,PythonからQX1との接続を確立します.
d = Discoverer()
c = d.discover()
cam = c[0]
versions = cam.get_versions()
stream_url = cam.start_liveview()
stream = cam.stream_liveview(stream_url) #BGR順
QX1 -> OpenCV
QX1で取得できるライブビューストリームからnextで次々に画像(frame)を取得できます.frameは1枚のJPEG圧縮ファイルのバイナリデータです.OpenCVのimdecode関数でJPEGバイナリを復号して,numpy型に変換します.nextはfor/while文の中で使います.
cv2.namedWindow('img')
while True:
# QX1 -> JPEG
[frame, seq, ts] = stream.next()
nparr = np.fromstring(frame, np.uint8)
# JPEG -> OpenCV
cv_img = cv2.imdecode(nparr, cv2.CV_LOAD_IMAGE_COLOR) #BGR順
# (ここでリサイズ等のOpenCVによる色々な画像処理を実行)
# 表示
cv2.imshow('img', cv_img)
OpenCV -> Caffe
# OpenCV -> Caffe image
# (w,h)はCaffeのNWに入力可能なサイズ
cv_img = cv2.resize(cv_img, (w, h))
caffe_img = skimage.img_as_float(cv_img).astype(np.float32)
# (おまけ)
# Caffeによる推論を実行.ただしCaffeのモデルファイル読み込みはwhileの前にやっておく
net.blobs['data'].data[0] = transformer.preprocess('data', caffe_img)
result = net.forward()
# end while
cv2.destroyAllWindows()
その他
Caffeの推論処理とOpenCVの表示処理はシングルスレッドで回すと処理が重くなってカクカクすることがあります.Pythonのmultiprocessライブラリ等を使って,推論処理と表示処理を並列処理させると動作がスムーズです.
おわりに
本記事ではLinuxをSONYのデジカメと接続する方法,OpenCV/Caffeでライブビューを取り扱う方法を説明しました.実はSONY以外のカメラメーカーもSDKを提供しています.RICOHは中判645Zにも対応!アツい!
写真の美しさを評価するCaffeモデルを作成してJetsonに載せてデジカメを作り,CDS研究会で報告しました.
- 井上 義隆, 松村 択磨, 深澤 佑介, 山田 和宏, "深層学習による美的評価エンジンの開発と構図推薦カメラへの実装", 研究報告コンシューマ・デバイス&システム(CDS), 2018.
-
その他のデジカメの対応状況 https://developer.sony.com/ja/develop/cameras/api-information/supported-devices ↩
-
SonyPyのオリジナルコードは,MITライセンスで配布されており,Cart Logic, Inc. が著作権を保持しています.Copyright 2013 Cart Logic, Inc. Author: Scott Torborg, http://github.com/storborg/sonypy ↩