Pythonでブラウザから画像を受け取って、処理して返す。
クライアントがHTML、サーバーがPythonのWebアプリの勉強。ブラウザから画像を選択し、Pythonで微分画像にしてそれをブラウザでまた表示する、という内容。
結果
初期画面。左側に処理前の画像を表示し、右側に処理後の画像を表示する。
画像のアップロード(Pythonで処理)後の画面。
※入力フォームで画像を選んだ瞬間に左側に選択画像が表示されるが、サーバーから処理画像を返す時に、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に変換し、それをタグに渡す、という処理。
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サーバーを立てる!
- Pythonでクライアント・サーバーアプリケーションを作ってみたい
http.server
を使うことで自分のパソコンにサーバーを立てることができる。
解説
クライアント・サーバーアプリケーションは下図のイメージ。
サーバー
ブラウザからもらった要求に従って何らかの処理をする。(上図だと a × b を計算して、その結果 c をブラウザに返す)
サンプルプログラム
ブラウザに a と b の2つの数値を入力すると、a と b を掛け算した結果が表示される、というプログラムを作ってみる。
練習ということで、http.server
モジュールを使ってローカルサーバーを立てる。これでパソコン1台でクライアント・サーバーの動作を見ることができる。
ディレクトリの階層
pyファイルが2つ必要になる。一つはサーバー自体(cgiserver.py)、もう一つがブラウザに表示する内容(kakezan.py)。それぞれのpyファイルを保存する場所は以下のようにする。
サーバーへのアクセス方法
ブラウザのアドレスバーに 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 シフト量を与えた。右が元画像、左がアフィン変換後の画像。
プログラム
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で作ってみた。今回はラインプロファイルを表示するところまで。
結果
左はサイン波形でグラデーションになるように作った画像。黒線はマウスで引いた線で、リアルタイムに追従するのでどこに線を引いているかわかる。
右は引いた線に対応する輝度値をプロットしたもの。
プログラム
Python 3.8.1
opencv-python 4.1.2
opencv 4系ではlineiterator
がないので、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周期分のグラデーション画像が得られた。
プログラム
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]