ONVIF対応ANRANカメラをPythonから制御する方法

安価なネットワークカメラをPythonで制御する

ANRAN製の安価なネットワークカメラは、防犯用途だけでなく設備監視や画像処理システムにも活用できます。
しかし、Pythonから制御しようとすると、ONVIFライブラリでエラーが発生したり、公式情報が少ないため苦労することがあります。
本ページではANRAN P3Maxを例に、

・RTSPによる映像取得
・Pythonによる画像表示
・ONVIF PTZ(パン・チルト)操作

を実装する方法を紹介します。

ANRAN P3Max
Agent DVRで取得した画像

1.カメラ画像の取得

まずはCemaraCapture関数について説明します。
映像の取得にはrtspモジュールを利用します。
rtsp.client()を実行するとカメラとの接続が開始され、RTSPストリームの受信が始まります。
続いてclient.read()を呼び出すことで、最新の映像フレームををPIL.Image形式で取得できます。
本サンプルでは、取得した画像を OpenCV(cv2)の Mat 形式 に変換し、リアルタイムでウィンドウへ表示しています。

なお、ANRAN P3Maxでは以下のRTSPパスが利用できます。
・高解像度ストリーム:/Streaming/Channels/101
・低解像度ストリーム:/Streaming/Channels/102
高解像度ストリームは画質を重視する場合に適しており、低解像度ストリームは通信量やCPU負荷を抑えたい場合に有効です。用途に応じて使い分けてください。

import numpy as np
import cv2
import threading
import rtsp
from time import sleep

#host,user,passwordは各自のカメラで設定した値を入力
host = "192.168.***.***"
user = "admin"
password = "11111111"

isClosing = False

def CameraCaputure():
    camera_url = "rtsp://{}:{}@{}:8554/Streaming/Channels/102".format(user, password, host) 
    client=rtsp.Client(rtsp_server_uri=camera_url,verbose=True)
    
    while not isClosing:

        #フレーム情報取得
        frame=client.read(False)
        
        if frame==None:
            continue

        #PIL.Imageからcv2.Matへの変換
        mat = np.array(frame, dtype=np.uint8)
        if mat.ndim == 2: # モノクロ
            pass
        elif mat.shape[2] == 3: # カラー
            mat = cv2.cvtColor(mat, cv2.COLOR_RGB2BGR)
        elif mat.shape[2] == 4: # 透過
            mat = cv2.cvtColor(mat, cv2.COLOR_RGBA2BGRA)
        
        #ウィンドウの表示
        cv2.imshow("camera",mat)
        cv2.waitKey(1)
        sleep(0.2)

    #メモリ開放 
    cv2.destroyWindow("camera")
    client.close

メイン関数は以下の通り記述し、CameraCapture関数を実行するスレッドを作成します。
Enterキー入力で終了するようにします。

if __name__ == '__main__':

    #カメラキャプチャー 初期化
    camera_thread = threading.Thread(target=CameraCaputure)
    camera_thread.daemon = True
    camera_thread.start()

    #カメラ位置初期化

    while True:

        key = input(' Enterキーを押したら終了します')
        if not key:
           break

    isClosing=True 
    camera_thread.join()

実行すると以下のようにカメラの画像がウィンドウに表示されます。

 

2. パン、チルト動作を行う

ANRAN P3Maxでは、一般的に使用される onvif-zeep ライブラリから接続すると例外が発生し、PTZ(パン・チルト・ズーム)制御を正常に実行できませんでした。
そこで今回は、Wiresharkでカメラとの通信内容を解析し、SOAPリクエストを直接送信する方法 を採用しています。

まずは、カメラを絶対位置へパンする操作で使用するSOAPメッセージのテンプレートとして 「AbsoluteMove.xml」 を作成します。
SOAPメッセージの内容は、汎用監視ソフト Agent DVR を使用してカメラを操作し、その際に送信されるパケットをWiresharkでキャプチャすることで確認できます。
XML内の記述については、UsernameTokenTo の要素以外はそのまま流用できます。
UsernameToken : カメラの認証情報
To:カメラのPTZサービスURL
これらは使用するカメラやネットワーク環境に合わせて設定してください。

<PanTilt x="1" y="-1" space="http://www.onvif.org/ver10/tptz/PanTiltSpaces/・・・のx, yの値はそれぞれパン方向(水平方向)、チルト方向(垂直方向)のカメラの絶対位置を指定しています。
対応する値は以下のようになります。
・x = “-1” : 左端(-180°)
・x = “1” : 右端(180°)
・y = “-1” : 下端(-90°)
・y = “1” : 上端(0°)
例えば、x="0“、y="0” を指定すると、カメラは中央方向、45°見下ろしの方向に向きます。

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope	xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <Security s:mustUnderstand="0" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <UsernameToken>
                <Username>admin</Username>
                <Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">(暗号化されたパスワード)</Password>
                <Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">(セキュリティキー)</Nonce>
                <Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2024-07-12T00:50:28.538Z</Created>
            </UsernameToken>
        </Security>
        <a:Action s:mustUnderstand="1">http://www.onvif.org/ver20/ptz/wsdl/AbsoluteMove</a:Action>
        <a:MessageID>urn:uuid:00000000000000000000000000</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <a:To s:mustUnderstand="1">http://192.168.11.***:8000/onvif/ptz_service</a:To>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <AbsoluteMove xmlns="http://www.onvif.org/ver20/ptz/wsdl">
            <ProfileToken>PROFILE_000</ProfileToken>
            <Position>
                <PanTilt x="1" y="-1" space="http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace" xmlns="http://www.onvif.org/ver10/schema"/>
            </Position>
        </AbsoluteMove>
    </s:Body>
</s:Envelope>
WireSharkによるパケット解析

必要に応じて初期位置移動、連続移動、移動停止のSOAPメッセージを記述するファイルを同様のやり方で準備します。

絶対位置移動は、プログラムの中で位置(変数)を変更したいこともあると思います。その場合はPythonのlxmモジュールを使用します。

以下は先ほど作成した「AbsoluteMove.xml」のx, yの値を変更する関数のサンプルです。

from lxml import etree

def SetAbsoluteMoveData(x, y):

    tree = etree.parse('AbsoluteMove.xml')
    root = tree.getroot()

    root.find('.//*[@x]').attrib['x'] = str(x)
    root.find('.//*[@x]').attrib['y'] = str(y)
    
    return etree.tostring(root, encoding='utf-8')

次にカメラ操作を実行するmain関数を記述します。

HTTPでの通信にはurllibモジュールを利用します。
実行により画像の位置にカメラが移動します。

import urllib.request
import urllib.response
import SetRequestCommand

def camera_control():
    print('IP Camera Test')

    #カメラ絶対位置移動
    ret=SetRequestXml.SetAbsoluteMoveData(-0.25, 1)
    req=urllib.request.Request(url="http://192.168.11.***:8000/onvif/ptz_service", data=ret,
                               headers={'Accept-Encoding':'gzip,deflate','Content-Type':'application/soap+xml; charset=utf-8'},method='POST')
    response = urllib.request.urlopen(req)

if __name__ == '__main__':
    camera_control()
プログラム実行後のカメラ位置