Python初心者がScikit-imageの例題を自分用に使うためのヒント
Python初心者がScikit-imageの例題を自分用に使うためのヒント2 複数のファイルを処理する
Python初心者がScikit-imageの例題を自分用に使うためのヒント3 ファイルに書き込む
Python初心者がScikit-imageの例題を自分用に使うためのヒント4 GUIを使う
に続いて、Scikit-imageの例題を、ほんの少しだけ改変して、遊んでみるために、Python初心者向けの内容を書いてみます。
今回は、Pythonの便利なネットワークライブラリを使ってみます。
Pythonには標準のライブラリだけでも[HTTPサーバー](BaseHTTPServer — 基本的な機能を持つ HTTP サーバ)や[POPクライアント](poplib — POP3 プロトコルクライアント)が書けるほどにライブラリが充実しています。それらをWindowsやLinuxやMacで共通に使えるという特徴をもっています。CPUのエンディアンの問題や文字コードの問題に頭を悩ませることなしにネットワークプログラムを書くことができます。
ブログ記事 Webカメラの画像をpythonのsocketを使って転送する
には、サーバー側のスクリプトとクライアント側のスクリプトがあります。サーバー側でカメラ画像を取得し、クライアント側でサーバーから画像を受け取り表示するものです。
Python 標準ライブラリ SocketServer — ネットワークサーバ構築のためのフレームワーク
とOpenCVの機能(import cv2でインポートされているもの)だけで、ネットワークカメラを実現することができます。
OS依存性もCPUのエンディアンの問題にもまったく悩ませられることなく実現しています。
今回は、このweb例題のwebサーバー上で画像を加工して画像を配信することを考えてみます。
サーバー側でOpenCVで取得した画像をエンコードする前に、画像を加工しましょう。
[Normalized Cut]
(http://scikit-image.org/docs/dev/auto_examples/segmentation/plot_ncut.html#example-segmentation-plot-ncut-py)
の例題で、入力画像を変えてみよう。
Normalized Cut を施した画像は、元画像から細かな特徴が失われるので、
プライバシーに若干配慮したネットワークカメラになるでしょうか。
ここでは、scikit-imageのNormalized Cutの例題から以下の部分を関数として取り出しました。
from skimage import segmentation, color
def plotNcut(img):
labels1 = segmentation.slic(img, compactness=30, n_segments=200)
out1 = color.label2rgb(labels1, img, kind='avg')
return out1
ブログ記事 Webカメラの画像をpythonのsocketを使って転送する
にあるサーバースクリプトでは
import cv2.cv as cv
を使っていますが、ここではnumpyのデータ形式を使うために
import cv2
として、以下のようにcv2のインタフェースを利用することにします。
cap = cv2.VideoCapture(cameraid)
ret, frame = cap.read()
jpegstring = cv2.imencode('.jpeg', frame)[1].tostring()
このように、Pythonにはネットワークのライブラリが充実しているので、その成果を簡単に取り入れて、scikit-imageやOpenCVを利用した画像処理・画像認識を組み込むことがとても簡単にできます。Windows上で作ったプログラムでも、ARMベースのLinux上でも容易に動作可能です。エンディアンの違うCPUでも、その違いを気にする必要がないのも利点です。
"""
webcamera server
for opencv 2.3
"""
import SocketServer
import cv2
from skimage import segmentation, color
def plotNcut(img):
labels1 = segmentation.slic(img, compactness=30, n_segments=200)
out1 = color.label2rgb(labels1, img, kind='avg')
return out1
class TCPHandler(SocketServer.BaseRequestHandler):
capture = ''
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print "%s connected:" % self.client_address[0]
ret, frame = cap.read()
frame = plotNcut(frame)
jpegstring = cv2.imencode('.jpeg', frame)[1].tostring()
print len(jpegstring)
self.request.send(jpegstring)
if __name__ == "__main__":
HOST, PORT = '127.0.0.1', 12345
#init camera
cameraid = 0
cap = cv2.VideoCapture(cameraid)
cap.set(3, 640)
cap.set(4, 480)
if not cap:
print "Could not open camera"
exit()
server = SocketServer.TCPServer((HOST, PORT), TCPHandler)
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.capture = cap
server.serve_forever()
'''
Image receiver
for OpenCV 2.4 python interface
'''
import socket
import numpy
import cv2
def getImageFromServer(HOST, PORT):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
sock.send('HELLO\n')
recvlen = 100
buffer = ''
while recvlen > 0:
receivedstr = sock.recv(1024*8)
recvlen = len(receivedstr)
buffer += receivedstr
print '%d bytes received' %len(buffer)
narray = numpy.fromstring(buffer, dtype='uint8')
decimg = cv2.imdecode(narray, 1)
sock.close()
return decimg
if __name__ == "__main__":
HOST, PORT = "127.0.0.1", 12345
# Receive data from the server and shut down
while 1:
img = getImageFromServer(HOST, PORT)
cv2.imshow('Capture', img)
key = cv2.waitKey(100)
if(int(key) > 27):
break
img = ''
以上のスクリプトは Webカメラの画像をpythonのsocketを使って転送する
を参考にしてほんのわずか改変したものです。client.pyの方は、ほとんどそのままです。このような連携を容易にできるのがPythonの魅力の1つです。
###追加:画像加工サーバー
サーバーでは画像の加工だけをする例を作成しました。
今度の例では、クライアントで撮影した画像を送信して
サーバーで加工した画像をクライアントに返す例です。
バイナリデータを送信するとき、バイナリデータの終わりが判別しがたいので、"DONE"という文字列を送信してそれが送られたときには、バイナリデータは既に終わりに達しているという方式にした。
参考:ソケットプログラミング HOWTO
#pylint:disable=C0103
"""
image processing server
"""
import SocketServer
import cv2
import numpy as np
from skimage import segmentation, color
def Ncut(img):
"""
Normalized Cut in scikit-image
"""
labels1 = segmentation.slic(img, compactness=30, n_segments=200)
out1 = color.label2rgb(labels1, img, kind='avg')
return out1
class TCPHandler(SocketServer.BaseRequestHandler):
capture = ''
def handle(self):
"""
Image processing server
"""
# self.request is the TCP socket connected to the client
recvlen = 100
buff = ''
while recvlen > 0:
receivedstr = self.request.recv(1024*8)
if receivedstr.find("DONE") > -1:
break
recvlen = len(receivedstr)
buff += receivedstr
print '%d bytes received' % len(buff)
narray = np.fromstring(buff, dtype='uint8')
frame = cv2.imdecode(narray, 1)
print "decoded image", frame.shape
frame = Ncut(frame)
print "passed Ncut"
jpegstring = cv2.imencode('.jpeg', frame)[1].tostring()
print len(jpegstring)
self.request.send(jpegstring)
if __name__ == "__main__":
HOST, PORT = '127.0.0.1', 12345
server = SocketServer.TCPServer((HOST, PORT), TCPHandler)
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
#pylint:disable=C0103
'''
Image client
'''
import socket
import numpy as np
import cv2
def getProcessedImageFromServer(HOST, PORT, frame):
"""
send image and recieve proccessed image from the server
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
jpegstring = cv2.imencode('.jpeg', frame)[1].tostring()
print len(jpegstring)
sock.send(jpegstring)
sock.send("DONE\n")
recvlen = 100
buff = ''
while recvlen > 0:
receivedstr = sock.recv(1024*8)
recvlen = len(receivedstr)
buff += receivedstr
print '%d bytes received' %len(buff)
narray = np.fromstring(buff, dtype='uint8')
decimg = cv2.imdecode(narray, 1)
sock.close()
return decimg
if __name__ == "__main__":
HOST, PORT = "127.0.0.1", 12345
# Receive data from the server and shut down
cameraid = 0
cap = cv2.VideoCapture(cameraid)
cap.set(3, 320)
cap.set(4, 240)
while 1:
ret, frame = cap.read()
img = getProcessedImageFromServer(HOST, PORT, frame)
cv2.imshow('processed', img)
key = cv2.waitKey(100)
if int(key) > 27:
break
img = ''
###付記:BoostとC++で記述するときには
今わたしがC++でネットワークプログラムを書くとしたら、Boostを使うと思います。
[ip::tcp::socket]
(http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/reference/ip__tcp/socket.html)
boostjp : Boost日本語情報サイト
Qiita記事 Boost.Asio で Raw Socket を開くメモ
昔ながらのUnix由来のCのsocketライブラリ:
TCP/IPのネットワークを切り開いてきた時代のライブラリであり、新たに書き始めるのに使うべきライブラリとは思えません。
ネットワークが作られた以降に、unicodeが登場したり、標準化されたマルチスレッドが登場しています。Unix/Liunxの標準のライブラリは後方互換性が重要なために、それらの関数を捨てることができません。
Windows固有のsocketライブラリ:
Unixのsocketライブラリとかなり異なっており、そこで記述した関数をLinuxに移植するのは容易そうには思えません。
###付記:socketによる通信を用いることの意義
複数のプロセス間でsocketによる通信を利用すれば、
C++で実現されているプログラム <-> Pythonで実現されているプログラム
という方式で全体の機能を実現することができます。
そうすれば、PythonのプログラムをC++にリンクする。C++のプログラムをPythonからリンクして使えるようにするといった作業が不必要なまま、機能を連結することができます。
os.system(cmd)os — 雑多なオペレーティングシステムインタフェース
subprocess.Popen() subprocess — サブプロセス管理
を使う場合には、毎回プロセスを起動して終了するというとてもオーバーヘッドの高い手順になりますが、プロセスを起動しておいたままにしておいて、socket通信を用いて実現すれば、そのオーバーヘッドはありません。
###付記:Pythonを用いたwebサーバー
Pythonを用いたwebサーバーの例にはCherryPyがある。「実践コンピュータビジョン」ではCherryPyを用いた画像検索のwebサーバーの例が書かれている。
###付記:C++の場合だったら
C/C++の場合だったら、OpenCVや機械学習に関するプログラムを書けるスキルを持ちつつ、サーバープログラムとクライアントプログラムのsocketプログラムを同時に書けるスキルを持つことはとても難しいことです。WindowsでのsocketのプログラムとLinuxでのsocketが標準では異なっていることも、そのようなことを難しくしてしまう。