UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

Camera付きRaspberry PI Zero Wで、Python利用のWebサーバを構築し、Webブラウザで撮影画像を閲覧する作品例

このページで紹介したロボットにソースを追加して、Webブラウザで制御できるようにするまでの過程を残した情報です。
下記内容の続きとして、Webブラウザで制御(CGI利用)も出来る作品はこのページをご参照ください。
(下記画像のクリックでイメージが変わります。)

右上で示したように、Webブラウザでロボットが撮影した写真(静止画像:tscr.jpg)を閲覧できるようにした作品です。
(上記画像は、ロボットを部屋の中に置いて撮影した時の閲覧例です。スリッパが見えている!)
閲覧してているindex.html内のJavascriptのインターバルタイマーにより、1秒ごとにtscrXXXX.jpgファイル(XXXX部は連番)をsrcに設定しています。
一方、Webサーバ側はtscr〜.jpgが要求されると、撮影した画像を保存し、その画像で応答するようになっているので、 1秒ごとの画像が閲覧できる仕組みになっています。
なお、サーバでは、scrxxxx.jpg(xxxxの箇所は任意の数字)が要求されると、そのファイルが[imgs]フォルダ内に存在するするとそれを返すのですが、 要求したscrxxxx.jpgが存在しないと、新たに撮影してその画像をscrxxxx.jpgへ保存して返すようになっています。
それによって、scrxxxx.jpgファイル群が[imgs]フォルダに蓄えられるのですが、その内容はimgsのリンクより閲覧できるようにしています。
このページで紹介した構成の「/home/pi/umehoshi 」ディレクトリ内の直下に 以下の2つのファイルを配置することで実現させています。

なお、index.html内に、index.htmのリンク(制御ページへ)がありますが、 これはWebブラウザで制御(CGI利用)用のファイルで、それも使う作品にする場合は、続けてこのページで示した追加が必要です。

index.htmlの内容

このhttp://192.168.100.1/index.htlmで、1秒ごとにWebサーバへ連番画像ファイルをimgタグに設定しています。
このファイル名は最上部左端で背景を白で表示して、その応答でシアンの背景にしています。
(この変化で応答の動作を確認できます)

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">
    <meta http-equiv="Expires" content="0">

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width">
    <style>
        body {
            font-size: large;
            background-color:aquamarine;
            padding: 0;
            margin: 0;
        }
    </style>
    <title>Image </title>
<script type="text/javascript">
var timeId2;
var img_elements;	// 変更対象のimgタグ要素
var text_file;	// HTTPで要求する画像名の表示用
var count=0;	// 連番画像名用のカウント
function re_load(){ // インターバルタイマーで動作
    text_file.textContent = "tscr" + count++ + ".jpg"; // 連番のファイル名
    text_file.style.backgroundColor="white";
    img_elements.src = "imgs/" + text_file.textContent;// 「連番の画像ファイルの要求」
}

function init(){
    img_elements= document.getElementById("IMG_A");
    text_file = document.getElementById("CurrentImgName");
    img_elements.onload= function (e) {// 「連番の画像ファイルの要求」の応答処理
        text_file.style.backgroundColor="cyan";
	}
    timeId2 = setInterval(re_load, 1000);//1秒ごとに実行
}
</script>
</head>
<body onload="init()">
<text id="CurrentImgName">tscr.jpg</text>
<a href="index.htm">制御ページへ</a> <a href="imgs/">imgs</a><br>
<img id="IMG_A" name="img_main" src="imgs/tscr.jpg" width="100%">
</body>
</html>

myweb.pyの内容 (pythonで作ったWebサーバ本体のプログラム)

一般にブラウザでは、画像をキャッシュして、以前に得られてキャッシュファイルに画像があると、サーバにリクエストしない仕組みになっているようです。
ですから、サーバ側のレスポンスヘッダーでscr.jpgをキャッシュさせないようなヘッダー送出が無いと、ブラウザで見る画像が希望通りに更新されません。
下記に示すmyweb.pyのwebサーバでは、このキャッシュをさせない送出を行わせる対処が必要です。
do_GET(self)メソッドのオーバライドで、撮影の画像要求に対するキャッシュをさせない「'Cache-Control', 'no-store'」を送出させています。
tscrXXXX.jpg、scrXXXX.jpg、clear.jpg、quit.jpg、など特別な名前の画像要求にだけ応答の振る舞いを変更していますが、 それ以外の処理はsuper().do_GET()に任せています
複数の人が閲覧している時でも、tscrXXXX.jpg(XXXXの箇所は任意の数字)の要求で余計な撮影をしないように、 1秒ごとにしかscr.jpgの撮影をしない制御をしています。 これがhttp://192.168.100.1/の閲覧時の動作です。
この要求で、サーバ側に保存される状態には、2つモードがあります。(どちらも、要求されたtscr〜.jpgに関係ない保存名)
デフォルトは、固定ファイル名での保存モードで、'imgs/temp.jpg' の名前で保存されます。
もう一つは、'imgs/scrXXXX.jpg'のXXXXが連番部のファイル名でカウントアップする保存モードです。
(ブラウザからの'GET /imgs/tscr〜.jpg'にリクエストで1秒以上間隔の要求で作られます。)
このモードは、http://192.168.100.1/clear.jpgの閲覧で切り替わります。
この切り替わり時に、[imgs]のフォルダを全部削除して作り直す処理が含まれてます。

またimgs/scr〜.jpgが要求されると、そのファイルが[imgs]フォルダ内あるか調べて、あればsuper().do_GET()に任せた応答していますが、 無ければ、写真を撮影して'imgs/scr〜.jpg'に保存して、その画像で応答するようにしています。
例えば、http://192.168.100.1/imgs/scrABC.jpgで閲覧した時、 存在しなければ、撮影してimgs/scrABC.jpで保存し、再びimgs/scrABC.jp閲覧すれば前に撮った写真が見えます。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#   「sudo python3 myweb.py」で動かす

#------------------------ 「CGIで[UMEHOSHI ITA ]を制御する際の初期転送」--ここから
#import umeusb
#umeusb.init_sub()
#start_path = "datas/app_pwm_pizero.c.hex"
#umeusb.send_cmdfile(start_path)
#umeusb.send_cmd("R00800050000061")
#umeusb.usb.close()
#------------------------ 「CGIで[UMEHOSHI ITA ]を制御する際の初期転送」--ここまで

import http.server
from http.server import HTTPServer, CGIHTTPRequestHandler
import time
import threading
import os

import picamera
#import cv2 # OpenVC のカメラ利用

import shutil # シェル操作用モジュール(shell utility) 

def  clearImgs(): # 画像ディレクトリ削除
    if os.path.isdir("./imgs/") == True:
        shutil.rmtree('./imgs') # imgsフォルダを中身ごと削除
    #
    os.mkdir('./imgs') # imgsフォルダを新規作成
    print("clearImgs")

os.chmod('/dev/ttyACM0',0o666)
camera = picamera.PiCamera() # カメラをオープンする(カメラ準備処理)
#camera = cv2.VideoCapture(0) # カメラをオープンする(カメラ準備処理)

#def save_capture(cv2, camera, request_file):
#    success, frame_img = camera.read() # カメラ画像取得する。
#    frame_img = cv2.resize(frame_img, (600,400))
#    cv2.imwrite(request_file,frame_img) 
#    print(success,"-------------------------", request_file)

my_webserver=[None]
request_file ="" # ブラウザから要求した画像ファイルの相対パスが記憶される

class Handler(CGIHTTPRequestHandler): # CGI対応のサブクラス
    next_time=0
    flagFixed=True # 固定ファイル名での保存モード
    scrXXXX = 'imgs/scr0000.jpg' # 連番時のファイルパス
    count=0 # 連番時用のカウント
    cgi_directories = ["/cgi-bin"]
    body = None
    def do_GET(self):
        global request_file
        s=self.requestline
        if s.upper().startswith("GET /CLEAR.JPG "): 
            Handler.count = 0
            Handler.scrXXXX = 'imgs/scr0000.jpg'
            Handler.flagFixed = not Handler.flagFixed # 連番保存ファイル切り替え
            clearImgs()
        if s.upper().startswith("GET /QUIT.JPG "):
            request_file = 'imgs/stop.jpg'
            return super().do_GET()
        #
        print("do_GET:", s)
        i2 = s.find('.jpg')
        if i2 == -1 : return super().do_GET()
        i2 += len('.jpg')
        i1 = s.rfind('imgs/', 0, i2) # i2より前
        if i1 == -1 : return super().do_GET()
        request_file = s[i1:i2]  # 設定例 'imgs/scr.jpg' 
        flag_exist = os.path.isfile(request_file)
        flagT = request_file.startswith('imgs/tscr')
        flagN = request_file.startswith('imgs/scr')
        if (flagT or flagN) == False: return super().do_GET()
        print("request_file:" + request_file , flag_exist)
        #
        if flagT:
            request_file = Handler.scrXXXX  # 連番時のファイル指定時の設定
            if Handler.flagFixed: request_file = 'imgs/temp.jpg'  # 固定時のファイル指定時の設定
            if Handler.next_time < time.time():
                Handler.next_time = time.time() + 1 # 次の実行を1秒後に設定
                Handler.count+=1
                Handler.scrXXXX = 'imgs/scr{:04}.jpg'.format( Handler.count ) 
                if not Handler.flagFixed: request_file = Handler.scrXXXX
                camera.capture(request_file) # 写真撮影
                #save_capture(cv2, camera, request_file)
            #
        elif flagN and flag_exist == False:
            camera.capture(request_file) # 存在しないファイルで、srcXXXXのファイルなら、それに写真撮影
            #save_capture(cv2, camera, request_file)
            #print("-------------------------", request_file)
        #
        with open(request_file, "rb") as f:
            Handler.body=f.read()
        #
        self.send_response(200)#成功レスポンス番号
        self.send_header('Cache-Control', 'no-store') # キャッシュさせない指定
        self.send_header('Content-type', 'image/jpeg')
        self.send_header('Content-length', len(Handler.body))
        self.end_headers()
        self.wfile.write(Handler.body)

server_address = ("", 80)
handler_class = Handler #ハンドラを設定
my_webserver[0] = HTTPServer(server_address, handler_class)
print( server_address )
while True:
     if my_webserver[0] is None: break
     my_webserver[0].handle_request ()
     print( "privious request_file:" +request_file ) 
     time.sleep(0.01)
     if request_file == 'imgs/stop.jpg':
          #my_webserver[0].shutdown() # これを使うとブロックするので使わない
          break

print( "END HTTP SERVER.")
camera.close()
#camera.release(); cv2.destroyAllWindows()
camera=None

上記、Webサーバ(myweb.py)の実行方法

このページで紹介ように構築している場合、 192.168.100.1にSSHのクライアントを使って[Raspberry PI]を操作します。
(「pi」のIDで[abc123]のパスワードで接続)
「sudo systemctl disable umehoshi.service」でサービスを無効にした状態にします。
そして、「sudo python3 myweb.py」で次の例のように実行させます。

pi@raspberrypi:~ $ cd umehoshi/
pi@raspberrypi:~/umehoshi $ sudo python3 myweb.py
request_file:imgs/scr.jpg True
192.168.100.5 - - [23/May/2022 17:00:13] "GET /imgs/scr.jpg HTTP/1.1" 200 -
privious request_file:imgs/scr.jpg
・・・・・
equest_file:/quit.jpg False
192.168.100.5 - - [23/May/2022 17:01:00] code 404, message File not found
192.168.100.5 - - [23/May/2022 17:01:00] "GET /imgs/stop.jpg HTTP/1.1" 404 -
privious request_file:imgs/quit.jpg
END HTTP SERVER.
pi@raspberrypi:~/umehoshi $

この状態で、ブラウザで「http://192.168.100.1/index.html」とアクセスすれば、 約1秒ごとにロボットで撮影した写真を閲覧数することができます。
そして、例えば「http://192.168.100.1/imgs/scr99.jpg」のアクセスで、 その時点で撮影した写真をscr99.jpgに記憶して閲覧でします。
また、[imgs]のリンクより過去に撮影したファイル群を閲覧できます。


以上の動作が確認できた後のCGI制御を含む構築作業は、このページで示します。(制御ページへのリンクの構築関連です。)

上記プログラムを終了させる場合、ブラウザで「http://192.168.100.1/imgs/quit.jpg」をアクセスします。(上記実行例は、この操作で止めています)
また、'imgs/'のディレクトリを初期化する隠しコマンドのような仕組みとしてclearImgs()関数を用意しています。
clearImgs()関数を実行させる場合、ブラウザで「http://192.168.100.1/clear.jpg」をアクセスします。

my_webserver[0] = HTTPServer(server_address, handler_class)で得られたサーバオブジェクトを生成した後、 my_webserver[0].serve_forever()でサーバを起動する方法もあります。
しかし、serve_forever()を使うとプログラムの終了が出来なかったので、handle_request()を呼び出す繰り返しにしました。
そして、このループ終了でプログラムを終了できるようにしました。


なお隠しコマンド操作として、ブラウザで'http://192.168.100.1/quit.jpg'で閲覧すると、 サーバのプログラムを終了します。