LoginSignup
7
7

More than 5 years have passed since last update.

Python初心者がScikit-imageの例題を自分用に使うためのヒント5 ネットワークアプリに組み込む

Last updated at Posted at 2016-01-19

Python初心者がScikit-imageの例題を自分用に使うためのヒント
Python初心者がScikit-imageの例題を自分用に使うためのヒント2 複数のファイルを処理する
Python初心者がScikit-imageの例題を自分用に使うためのヒント3 ファイルに書き込む
Python初心者がScikit-imageの例題を自分用に使うためのヒント4 GUIを使う
に続いて、Scikit-imageの例題を、ほんの少しだけ改変して、遊んでみるために、Python初心者向けの内容を書いてみます。

今回は、Pythonの便利なネットワークライブラリを使ってみます。
Pythonには標準のライブラリだけでもHTTPサーバーPOPクライアントが書けるほどにライブラリが充実しています。それらをWindowsやLinuxやMacで共通に使えるという特徴をもっています。CPUのエンディアンの問題や文字コードの問題に頭を悩ませることなしにネットワークプログラムを書くことができます。

ブログ記事 Webカメラの画像をpythonのsocketを使って転送する

には、サーバー側のスクリプトとクライアント側のスクリプトがあります。サーバー側でカメラ画像を取得し、クライアント側でサーバーから画像を受け取り表示するものです。

Python 標準ライブラリ SocketServer — ネットワークサーバ構築のためのフレームワーク
とOpenCVの機能(import cv2でインポートされているもの)だけで、ネットワークカメラを実現することができます。

OS依存性もCPUのエンディアンの問題にもまったく悩ませられることなく実現しています。

今回は、このweb例題のwebサーバー上で画像を加工して画像を配信することを考えてみます。

サーバー側でOpenCVで取得した画像をエンコードする前に、画像を加工しましょう。

Normalized Cut
Normalized Cut
の例題で、入力画像を変えてみよう。

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でも、その違いを気にする必要がないのも利点です。

server.py
"""
 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()

client.py
'''
  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

server2.py
#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()
client2.py
#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
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が標準では異なっていることも、そのようなことを難しくしてしまう。

ヒント6 Pythonのコードを改善する

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7