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

Pythonでブラウザから画像データを受け取る

ブラウザでローカルに保存している画像を選択し、それをPythonに渡す。Python側でOpenCVで画像処理をする、という想定。今日のところは画像を保存するところまで。画像はドキュメントルートに保存される。
コードの解説(忘備録)は後日、新しい記事としてポストする。

プログラム

Python3.8.1

# coding: utf-8

import cgi
import sys
import io
import base64
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>
</head>
<body>
    <form action="14_cgi_image_001.py" method="post" enctype="multipart/form-data">
    <input type="file" name="imagefile">
    <input type="submit" value="保存する">
    </form>
</body>
</html>"""

form = cgi.FieldStorage()
img = form.getvalue("imagefile")

try:
#    after_encode = base64.b64encode(img)
#    after_encode = str(after_encode)
#    after_encode = after_encode.replace("b'","")
#    after_encode = after_encode.replace("'","")
#    print("<img src='data:image/jpeg;base64,{0}'>".format(after_encode))

    arr = np.asarray(bytearray(img), dtype=np.uint8)
    d_img = cv2.imdecode(arr, -1)
    cv2.imwrite('Converted.jpg', d_img)
    print("保存しました")
    
except:
    print(html_body)

[14_cgi_image_001.py]

PythonでWebサーバーを立てる!

2021/01/27 更新

Pythonでクライアント・サーバーアプリケーションを作ってみたい
http.serverを使うことで自分のパソコンにサーバーを立てることができる。

解説

クライアント・サーバーアプリケーションは下図のイメージ。

python-client-server

クライアント

例えばChromeなどのWebブラウザがクライアントになり、ブラウザからサーバーに何らかの要求を出す。(上図だと a × b を計算する要求を出す)

サーバー

ブラウザからもらった要求に従って何らかの処理をする。(上図だと a × b を計算して、その結果 c をブラウザに返す)

サンプルプログラム

ブラウザに a と b の2つの数値を入力すると、a と b を掛け算した結果が表示される、というプログラムを作ってみる。
練習ということで、http.serverモジュールを使ってローカルサーバーを立てる。これでパソコン1台でクライアント・サーバーの動作を見ることができる。
python-client-server

ディレクトリの階層

pyファイルが2つ必要になる。一つはサーバー自体(cgiserver.py)、もう一つがブラウザに表示する内容(kakezan.py)。それぞれのpyファイルを保存する場所は以下のようにする。
python-client-server


サーバーへのアクセス方法

ブラウザのアドレスバーに kazezan.py のパスを入力する。具体的には以下のように。

http://localhost:8000/cgi-server/kakezan.py

(注)"localhost" 部分をローカルループバックアドレス(127.0.0.1)にするよう紹介しているサイトがあったが、自分の環境では動作しなかった。

ソースコード

サーバー
# cgiserver.py
# python 3.8.1

import http.server

server_address = ("", 8000)
handler_class = http.server.CGIHTTPRequestHandler # ハンドラを設定
server = http.server.HTTPServer(server_address, handler_class)
server.serve_forever()
ブラウザの表示内容
# kakezan.py
# python 3.8.1
# coding: utf-8

import cgi
import sys
import io
import re

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に代入している
## %sで変数ssを受けている
html_body = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Python CGI program</title>
</head>
<body>
    <p>aとbに数字を入れてください。</p>

    <form action="/cgi-bin/13_sum_001.py" method="post">
        <span>a: </span><input type="text" name="text1"><br>
        <span>b: </span><input type="text" name="text2"><br><br>
        <input type="submit" name="submit" value="計算">
    </form>

    <p>a x bは %s です。</p>
    
</body>
</html>"""

form = cgi.FieldStorage()
a = form.getvalue('text1', '')                                              #FieldStorageの中からキーがtext1の要素を拾う
b = form.getvalue('text2', '')                                              #FieldStorageの中からキーがtext2の要素を拾う

if re.compile("^\d+\.?\d*\Z").match(a) and re.compile("^\d+\.?\d*\Z").match(b):     #aとbが数値であるかどうかを調べる
    aa = float(a)
    bb = float(b)
    ss = round(aa * bb, 5)

else:
    ss = "N/A"
    
print (html_body % (ss))                                                    #cgiプログラムにおけるprintはクライアントへの返信。

Pythonで画像変換(アフィン変換)

物体認識した対象が画像の中央に位置するように画像を変換(XYシフト)する、というプログラムが作りたい。今日のところは、シフト量が与えられているものとして、アフィン変換の部分だけを作った。

結果

400 x 200 pixの画像に x= 20, y= 50 シフト量を与えた。右が元画像、左がアフィン変換後の画像。
f:id:greenhornprofessional:20200204235050p:plain

プログラム

Pyhton3.8.1

import sys
import cv2
import numpy as np

#アフィン変換(XYシフト)クラス
class AffineShift():
    
    #入力画像をコンストラクタで受ける
    def __init__(self, img):
        self.img = img
        self.x = img.shape[0]                                   #imgの横サイズ
        self.y = img.shape[1]                                   #imgの縦サイズ
        print("Start")

    #アフィン変換メソッド    
    def shift(self, row, col):                                  #シフト量をrow, colで受ける                              
        M = np.float32([[1,0,row],[0,1,col]])                   #変換行列の作成(np.float32必須)
        dst = cv2.warpAffine(self.img, M, (self.y, self.x))     #変換してdstに画像を入れる
        cv2.imshow('Original image', self.img)
        cv2.imshow('Converted', dst)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

#メインメソッド
def main():
    dx = 20                                                     #横方向のシフト量
    dy = 50                                                     #縦方向のシフト量
    img = cv2.imread(sys.argv[1], cv2.IMREAD_COLOR)
    affineShift = AffineShift(img)                              #インスタンス作成
    affineShift.shift(dx, dy)                                   #shiftメソッド実行

if __name__ == '__main__':
    main()

[11_AffineShift_001.py]

Pythonでマウスで線を引いて画像からラインプロファイルを抽出する

市販のHalocn、フリーならImageJなどで、画像からラインプロファイルを抽出することができる。ラインプロファイルを解析するところまで1個のソフトでやりたかったのでPythonで作ってみた。今回はラインプロファイルを表示するところまで。

結果

左はサイン波形でグラデーションになるように作った画像。黒線はマウスで引いた線で、リアルタイムに追従するのでどこに線を引いているかわかる。
右は引いた線に対応する輝度値をプロットしたもの。
f:id:greenhornprofessional:20200125194412p:plain

プログラム

Python 3.8.1 opencv-python 4.1.2

opencv 4系ではlineiteratorがないので、stackoverflowにあったコードdef bresenham_marchを使わせてもらった。1か所だけ変更している。詳細はリンク参照。

import numpy as np
import cv2
import math
from matplotlib import pylab as plt

#grobal変数の宣言
drawing = False
comp_roi = False
sx, sy, ex, ey = -1, -1, 0, 0

#ブレゼンハムアルゴリズム。指定した2点がつくる線分を構成する座標と輝度値を返す。
def bresenham_march(img, p1, p2):
    x1 = p1[0]
    y1 = p1[1]
    x2 = p2[0]
    y2 = p2[1]
    #tests if any coordinate is outside the image
    if (   x1 >= img.shape[0]
        or x2 >= img.shape[0]
        or y1 >= img.shape[1]
        or y2 >= img.shape[1]
    ): #tests if line is in image, necessary because some part of the line must be inside, it respects the case that the two points are outside
        if not cv2.clipLine((0, 0, *img.shape), p1, p2):
            print("not in region")
            return

    steep = math.fabs(y2 - y1) > math.fabs(x2 - x1)
    if steep:
        x1, y1 = y1, x1
        x2, y2 = y2, x2

    # takes left to right
    also_steep = x1 > x2
    if also_steep:
        x1, x2 = x2, x1
        y1, y2 = y2, y1

    dx = x2 - x1
    dy = math.fabs(y2 - y1)
    error = 0.0
    delta_error = 0.0
    # Default if dx is zero
    if dx != 0:
        delta_error = math.fabs(dy / dx)

    y_step = 1 if y1 < y2 else -1

    y = y1
    ret = []
    for x in range(x1, x2):
        p = (y, x) if steep else (x, y)
        if p[0] < img.shape[0] and p[1] < img.shape[1]:
            #ret.append((p, img[p]))
            ret.append([p[0], p[1], img[p[0]][p[1]]])   #この1文だけオリジナルから変更している
        error += delta_error
        if error >= 0.5:
            y += y_step
            error -= 1
    if also_steep:  # because we took the left to right instead
        ret.reverse()
    return ret

#マウスイベントから座標を返す
def draw_line(event, x, y, flags, param):
    global sx, sy, ex, ey, drawing, comp_roi

    if event == cv2.EVENT_LBUTTONDOWN:  #左クリック押下時
        drawing = True
        sx = x      #始点x
        sy = y      #始点y
        ex = x      #終点x …ライン描画されてしまうので、隠すために始点と同じ値にする
        ey = y      #終点y …ライン描画されてしまうので、隠すために始点と同じ値にする
        print("Left-clicked. Start point:(%d, %d)" %(sx, sy))

    elif event == cv2.EVENT_MOUSEMOVE:  #左クリックでドラッグ中
        if drawing == True:
            ex = x
            ey = y            
            #print ("Drawing...")  #デバッグ用。これがあるとライン描画が遅くなる
        else:
            pass
        
    elif event == cv2.EVENT_LBUTTONUP:  #左クリック開放時
        drawing = False
        print("Left-releasec. End point:(%d, %d)" %(ex, ey))
        comp_roi = True

img = cv2.imread('SinWave_512_512_1.jpg', cv2.IMREAD_GRAYSCALE)
temp = img.copy()
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_line)

#ループ処理。drawingとcomp_roiの状態変化を監視している。
while(1):
    cv2.imshow('image',temp)
    
    #左クリックドラッグ中のライン描画をリアルタイムで描画する
    if drawing:                                     #if(drawing == True): の省略系
        temp = img.copy()                           #これがないと描画が更新されない
        cv2.line(temp,(sx,sy),(ex,ey),(0,255,0),1)  #ライン描画

    #ライン描画が完了したらラインプロファイルを表示する
    if comp_roi:
        comp_roi = False
        P1 = (sy, sx)
        P2 = (ey, ex)
        v = bresenham_march(img, P1, P2)            #ブレゼンハムアルゴリズムに始点と終点を渡す
        f = []
        t = np.arange(len(v))                       #ラインプロファイルの横軸。!要改善!
        for i in range(len(v)):
            f.append(v[i][2])                       #配列vから輝度値だけを配列fに入れる
        plt.plot(t, f)
        plt.show()

    k = cv2.waitKey(1) & 0xFF
    if k == 27:                                     #Escボタンがクリックされたらループを終了する
        break

cv2.destroyAllWindows()

[10_LineProfile_001.py]

課題

  • ラインプロファイルの横軸が0から開始になってる。始点終点の情報を使うか。これは簡単に対応可。
  • 引いた線に対して垂直方向から見たラインプロファイル表示になっていない。XかYから見ている。

Pythonで不等号のTrue/Falseを変数に代入する

使う機会があるわからないが、右辺と左辺を比較して不等号が成立しているかどうかの結果を変数に代入する、というやり方。

結果

左辺 = 8.0
右辺 = 19.0
左辺 > 右辺か? False
左辺 < 右辺か? True

プログラム

Python3.8.1

import math

y1=2
y2=10
x1=1
x2=20

#左辺と右辺を比較して、不等号が成り立っていればTrue, 否であればFalseを代入する
a = math.fabs(y2 - y1) > math.fabs(x2 - x1)
b = math.fabs(y2 - y1) < math.fabs(x2 - x1)

print("左辺 = %1.1f" % (math.fabs(y2-y1)))
print("右辺 = %1.1f" % (math.fabs(x2-x1)))
print("左辺 > 右辺か?", a)
print("左辺 < 右辺か?", b)

[09_CalcBoolean_001.py]

PythonでSin波のグラデーション画像を作る

画像処理のテスト用画像として、サイン波状にグラデーションになっている画像が欲しかったので、Pythonで作ってみた。

結果

4周期分のグラデーション画像が得られた。
f:id:greenhornprofessional:20200121235901j:plain

プログラム

python3.8.1

from matplotlib import pylab as plt
import numpy as np
import cv2

pix = 512                            #画像のサイズ
img = np.zeros((pix,pix,1),np.uint8) #512行1列の2次元配列を512個、という3次元配列。画素値は8bit。カラー24bitにするときは1→3に。
x = np.arange(pix)                   #0~511までのX軸を作成
f = 100 * np.sin(2 * np.pi * 5 * x / pix) + 150  #5周期分のサイン波を作成。振幅(画素値)は適当に。

#デバッグ用
#print(f.max())
#print(f.min())

#サイン波をimgに代入する
for i in range(pix):
    for j in range(pix):
        img[j][i] = f[i]

#デバッグ用
#plt.plot(x, f)
#plt.show()

#imgを画像として保存
cv2.imwrite('SinWave_512_512_1.jpg', img)

[08_SinWave512pix_002.py]

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