ANRANカメラを独自アプリで制御する

省人化やDXの流れもあり、設備にもWebカメラを設置し設備や人の作業を監視することも多くなってきました。
今回はネットワークカメラ(IPカメラ)を設備と連携できるように、独自のアプリで動かしたいときに必要なカメラ画像を取得する方法と、パン・チルトを行う方法を紹介します。

カメラはOnvifに対応する安価なカメラ「ANRAN P3Max」をPyhonで動かしてみます。

ANRAN P3Max
Agent DVRで取得した画像

1.カメラ画像の取得

まずはCemaraCapture関数の記述です。画像の取得にはrtspモジュールを利用します。
rtsp.client()でカメラとの画像データ受信が始まり、client.read()で画像データをPIL.Imageデータに割り当てます。
今回は取得した画像データをOpenCVのMat形式に変換し、ウィンドウに表示しています。

カメラのURLは本カメラでは、高解像度:”~/Streaming/Channels/101″,低解像度,”~/Streaming/Channels/102″に対応しており、用途に応じて設定、切替を行ってください。

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.パン、チルト動作を行う

パン、チルトなどのカメラ操作を行うにはonvif-zeepモジュールを使用することが多いが、本カメラでは例外が発生するため、onvif-zeepを使用せずにカメラを制御しています。

まずは”AbsoluteMove.xml”を作成します。
要素UsernameTokenと要素To以外はコピー可です。UsernameTokenToは自身の環境に合わせて設定ください。
(Wiresharkなどのパケットキャプチャーを記録した状態で、Agent DVRのような汎用カメラアプリでカメラを動作したときのパケットを解析するとUsernameTokenが調べられます)

xmlファイルの読み込みにはlxmlモジュールを利用します。
引数xは-1で-180°、1で+180°の位置を示します。
引数yは-1で-45°、1で+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によるパケット解析

次にXmlデータをhtml形式に変換する関数を記述します。

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.5,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()
実行後のカメラの位置