Python独習!

習得したPython知識をペイフォワード

Pythonでブラウザから画像を受け取って、処理して返す。

クライアントがHTML、サーバーがPythonのWebアプリの勉強。ブラウザから画像を選択し、Python微分画像にしてそれをブラウザでまた表示する、という内容。

結果

初期画面。左側に処理前の画像を表示し、右側に処理後の画像を表示する。
f:id:greenhornprofessional:20200224221801p:plain

画像のアップロード(Pythonで処理)後の画面。
f:id:greenhornprofessional:20200224221821p:plain
※入力フォームで画像を選んだ瞬間に左側に選択画像が表示されるが、サーバーから処理画像を返す時に、HTMLをすべて読み込ませ直すため、選択画像が保持できない。今見えてるのはサーバーに保存された選択画像。なんとかスマートに作れないものか…

プログラム

全体

# 15_cgi_image_001.py
# python 3.8.1
# coding: utf-8

import cgi
import sys
import io
import cv2
import numpy as np

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding = 'utf-8')        #printで出力する内容をutf-8にする
print('Content-Type: text/html; charset=UTF-8\n')                           #ファイルの種類を定義

#ブラウザに表示する内容を記述
## HTMLを変数html_bodyに代入している
html_body = """
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Python CGI Image</title>

    <style>
        .box{
            margin         : left;
            box-sizing     : border-box;
            border         : 1px solid #666;
            width          : 620px;
            max-height     : auto;
            display        : flex;
            justify-content: center;
            flex-wrap      : wrap;
        }

        .box div img{
            box-sizing    : border-box;
            border        : 2px solid #35557f;
            width         : 300px;
            height        : auto;
            margin        : 2px;
        }
    </style>
</head>

<body>
    <form name="fm" action="15_cgi_image_001.py" method="post" enctype="multipart/form-data">
        <input type="file" id="imagefile" name="imgfile">
        <input type="submit" value="変換する">
    </form>
    <br>

    <div class="box" id="makeImg">
        <div>
            <img id="orgImg" src="%s" alt="Original Image">
        </div>
        <div>
            <img id="rsltImg" src="%s" alt="Result Image">
        </div>
    </div>

    <p>%s</p>
    
    <script type="text/javascript">
        var elem = document.getElementById("orgImg");
        <!-- console.log(elem.src); --->
        document.getElementById("imagefile").addEventListener("change", function(){
            var fileList = this.files ;
            <!-- console.log(fileList); --->
            <!-- console.log(fileList[0]); --->
            var blobUrl = window.URL.createObjectURL(fileList[0]);
            <!-- console.log(blobUrl); --->
            elem.src = blobUrl;
         });
    </script>
</body>
</html>"""

form = cgi.FieldStorage()
img = form.getvalue("imgfile")                          #form.getvalue(name)なので注意。idだとダメ。
txt1 = "初期化されました"
txt2 = "保存しました"
txt3 = "http://localhost:8000/NoImage.jpg"              #初期用のダミー画像。./NoImage.jpg だと何故かダメだったのでフルパス
txt4 = "http://localhost:8000/NoImage.jpg"

try:                                                    #読み込み一発目はエラーになる。一発目はexceptの処理が走る。
    arr = np.asarray(bytearray(img), dtype=np.uint8)    #バイナリデータを一度nparrayで受ける
    d_img = cv2.imdecode(arr, -1)                       #cv2でデコードする
    cv2.imwrite('OriginalImage.jpg', d_img)             #アップロードされた画像をドキュメントルートに保存する
    txt3 = "http://localhost:8000/OriginalImage.jpg"    #このやり方だと、アップロード前に表示できてたorg画像は消えてしまうので、改めてorg画像を表示させる
    edge = cv2.Canny(d_img,100,200)                     #ここにやりたい画像処理を入れる(今回は微分処理だけ)
    cv2.imwrite('ResultImage.jpg', edge)
    txt4 = "http://localhost:8000/ResultImage.jpg"      #処理後の画像をhtmlに表示するためのパス
    print(html_body % (txt3, txt4, txt2))               #HTML文中にある%sにそれぞれ代入してからHTML表示する
    
except:
    print(html_body % (txt3, txt3, txt1))

HTML部分のコメント

HTML部分はソースにコメント書きにくいので以下にまとめる。

    <form name="fm" action="15_cgi_image_001.py" method="post" enctype="multipart/form-data">
        <input type="file" id="imagefile" name="imgfile">
        <input type="submit" value="変換する">
    </form>

注意点はenctypeで、ファイルのアップロードがある場合は、とりあえずmultipart/form-dataにしておけば間違いない、らいしい。
また、入力フォームの"id"と"name"は似てるけど、省略できない。それぞれgetElementById、form.getvalueに使われている。

    <div class="box" id="makeImg">
        <div>
            <img id="orgImg" src="%s" alt="Original Image">
        </div>
        <div>
            <img id="rsltImg" src="%s" alt="Result Image">
        </div>
    </div>

処理前の画像を左に、処理後の画像を右に、横並びするためにdiv入れ子にしている。

    <script type="text/javascript">
        var elem = document.getElementById("orgImg");
        <!-- console.log(elem.src); --->
        document.getElementById("imagefile").addEventListener("change", function(){
            var fileList = this.files ;
            <!-- console.log(fileList); --->
            <!-- console.log(fileList[0]); --->
            var blobUrl = window.URL.createObjectURL(fileList[0]);
            <!-- console.log(blobUrl); --->
            elem.src = blobUrl;
         });
    </script>

画像をアップロードする前にブラウザに表示させるためのスクリプト
ローカル画像はタグで取り扱うために、パスをBlobしないといけないらしい。詳細は未勉強。入力フォームの状態が変化したら、その値をBlob urlに変換し、それをタグに渡す、という処理。

参考サイト

ローカル画像読み込みに関する内容
lab.syncer.jp
hakuhin.jp
techacademy.jp

Pythonからブラウザに画像を渡す方法
teratail.com

/* -----codeの行番号----- */