ローカルネットワーク上の Raspberry Pi を使って、バーコードリーダの Web アプリを作ってみます。
以下の環境で動作確認をしています。
環境:
・ Raspberry Pi (bullseye、サーバーとして使用、OpenCV をインストール済み)
・ Windows パソコン(クライアントとして使用)
・ Wi-Fi ネットワーク(Raspberry Pi と Windows パソコンはネットワークに接続済み)
背景 ~ バーコードリーダの Web アプリ化!
以前に、Python を使って、バーコードリーダのスクリプトをまとめ、公開しています(下記の「バーコードリーダー 【QRコード対応】」参照)。
スタンドアロンのパソコンに USB カメラを接続すると、バーコードリーダとして機能します。
とはいえ、パソコンにいつも USB カメラが接続してあるとは限りません。
ローカルネットワーク内で Web アプリとして動くようになっていて、ブラウザのお気に入りからバーコードの写真をドラッグ&ドロップしても動くようになっていると、より活用度が高まります。
ということで今回は、バーコードリーダの Web アプリ化を試みます。
スタンドアロンで動くのであれば、Web アプリとしても動くはずです。
Web サーバーは何でもよいのですが、ローカルネットワークにつながっている Raspberry Pi を使うことにします。
Raspberry Pi には標準で Python と Flask が入っていますので、必要最小限の手間で、Web アプリを作ってみます。
ローカルネットワーク内の Raspberry Pi が、バーコードリーダとして機能する Web アプリになります。
設定手順
libzbar0、pyzbar のインストール
① バーコードの読み取りにあたり、パッケージ pyzbar と libzbar0 が必要です。
インストールしていない場合は、Raspberry Pi でターミナルを起動し、以下のコマンドで2つのプログラムをインストールしてください。
sudo apt-get install libzbar0
pip install pyzbar
※ 個人的に試したところ、libzbar0 をインストールしなくても動くようですが、下記外部リンクを参照すると、libzbar0 をインストールするよう書いてありますので、マニュアルの通り記載しています。
② 以下を参考に、バーコードを扱うためのフォルダ(”barcode1″)を Raspberry Pi 内に作成してください。
例: /home/pi/barcode1/
③ ②のフォルダ内で、テキストファイルを作成し、barcode_decode1.py という名前で下記のスクリプトをコピー&ペーストして保存してください。
例: /home/pi/barcode1/barcode_decode1.py
※ バーコードを読み取るスクリプトです。
このスクリプトは、単体でも動くように作ってあります。かつ、他の Python のスクリプトから呼び出しても動くように作ってあります。
※ このスクリプトとパスを Flask のスクリプト app.py から呼び出すことで、Web アプリ上でバーコードを読み取ります。
もし、上記のスクリプトの場所を変えた場合は、後述の app.py で、指定しているパスを書き換えて修正してください。
④ Raspberry Pi を Web サーバーとして動かすため、下記を参考に公開用のフォルダ(ルートディレクトリ)を作成してください。
例: home/pi/flask1/barcode1/
⑤ ④のフォルダ内に app.py という名前でテキストファイルを作成し、下記のサンプルスクリプトをコピー&ペーストして保存してください。
例: home/pi/flask1/barcode1/app.py
⑥ さらに、④のフォルダ内に “templates” という名前でフォルダを作成してください。
⑦ ⑥のフォルダ内に “barcode_decoder1.html” という名前でテキストファイルを作成し、下記のサンプルスクリプトをコピー&ペーストして保存してください。バーコードを読み取るための画面のテンプレートとなっており、Flask の app.py から参照して使います。
例: home/pi/flask1/barcode1/templates/barcode_decoder1.html
※ ②と④のフォルダをわけていますが、Web サーバーの公開フォルダ(④)と、個別の機能(バーコード読み取り)は本来、別の機能ですので、フォルダをわけています。後日、画像処理などの機能追加をする際、混乱しないよう意図しています。
使い方
Web サーバーの起動・停止
⑧ Raspberry Pi のターミナルを起動し、④のフォルダ(ドキュメントルート)に移動してください。
例:
cd /home/pi/flask1/barcode1
⑨ 以下のコマンドを実行してください。
flask run --host=0.0.0.0 --port=5000
→ ”Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)” という文字列が表示されたら、Web サーバーの起動成功です。
※ Web サーバーを停止する場合は、[ctrl] と [c] のキーを同時に押してください。
※ ここで、”5000″ の数値(ポート番号)は、他の数値に変更しても問題ありませんが、以下でブラウザで参照する際に、同じ値となるようにしてください。
ブラウザからのアクセス
⑩ ローカルネットワーク内にある別のパソコン(例:Windows パソコン)で、ブラウザを起動し、以下を参考に、⑨の Web サーバーにアクセスしてください。
例: 192.168.1.33:5000/barcode_decoder1
→ バーコード読み取りの画面が表示されたら、サイトへのアクセスまで成功です。
バーコードリーダの Web アプリの使い方
⑪ バーコード読み取りの画面で、「ここにファイルをドラッグ&ドロップ」となっている欄に、バーコードの画像データをドラッグ&ドロップしてください。
→ バーコードを読み取り、結果が表示されます。
※ png、jpg の画像フォーマットについて、動作確認をしています。
※ QRコードについても、動作確認をしています。
うまく動いたら
・ うまく動いたら、バーコードの画像をいくつかドラッグ&ドロップしてみてください。
ネット検索したバーコードの画像等をドラッグするようにすると、操作が簡単です。
ローカルにあるバーコードの画像データについても同様に、ドラッグ&ドロップで動作します。バーコードを撮影した写真でも(撮影条件等が良好であれば)動作します。
・ また、HTML を修正し、画面のデザインをもっと洗練したものにしてみてください。HTML を修正した場合は、Flask を再起動し、ブラウザの画面を再表示してください。
・ 今回、バーコードを読み取るスクリプト barcode_decode1.py を app.py から読み出して利用しています。他の機能についても同じようにスクリプトに記載することで、呼び出して使うことが可能です。
・ 前に、同様のネットワーク構成で、バーコードを作成する Web アプリも作っています。
ブラウザを使ってこれら2つの画面を同時に開き、Web アプリ上でバーコードを作って、作ったバーコードをドラッグ&ドロップで、他方の Web アプリ上で読み込ませる、といった動きをさせることが可能です。
スクリプトの説明
バーコード読み取りスクリプト barcode_decode1.py の説明
・ barcode_decode1.py は、バーコードを読み取るスクリプトです。単独でも動くように作っています。
・ コマンドプロンプトで、このスクリプトのあるフォルダに移動し、”python barcode_decode1.py [画像のパス]” + [enter] とすることで、バーコードを読み取ることができます。このスクリプトを下記の app.py から呼び出すことで、Web アプリ化しています。
・ 冒頭のインポート文で、使用するパッケージをインポートしています。from pyzbar.pyzbar import decode とした行で、バーコードの読み取りに関するパッケージを呼び出しています。
・ def decode1( image1 ) としたところで、バーコードを読み取る処理を定義しています。画像 image1 を渡すと、読み取った文字列 str1 を返します。
なお、書籍のバーコード ISBN のように、1回の撮影(画像)で2つのバーコードが入り込む場合があるため、複数のバーコードにも対応させています。バーコードの読み取りが複数成功した場合は、リターン区切りで文字列を返します。書籍のバーコードの写真などをドラッグして、動作を確認してみてください。
・ if __name__ == “__main__”: とした行以降は、barcode_decode1.py をコンソールから実行した場合に動くようにするためのスクリプトです。引数で画像のリンクを渡すと、OpenCV で画像を読み取ります。その画像を decode1() に渡すことで、バーコードを読み取ります。ウェブの機能を使わなくても、バーコードの読み取りが可能です。
Flask のスクリプト app.py の説明
・ 冒頭で、Flask その他で必要なパッケージをインポートしています。
・ sys.path.append() としたところの2行で、上記の barcode_decode1.py が入っているフォルダを指定し、スクリプト barcode_decode1.py を読み込んでいます。
・ app = Flask() の部分は Flask を使うための定型的な記載です。
・ def get_image1() は、クライアント側から送付された画像を読み取って、OpenCV で扱えるフォーマットでの画像を取得する関数です。
・ def get_image2() は、バーコードの画像をクライアント側の HTML に送るため、base64 という形式に変換する関数です。
バイナリーのデータを特殊文字を使わない平易な文字列(base64)のみで表すようにすることで、OS や言語が異なっていても、文字化けなどをすることなく、クライアントとサーバー間でのデータの送受信が可能になります。
・ @app.route(“/barcode_decoder1”) としたところで、サイトの /barcode_decoder1 にアクセスしたとき、barcode_decoder1() 関数を実行し、テンプレート barcode_decoder1.html を返すようにしています。
・ barcode_decoder1.html では、ドラッグ&ドロップされた画像を /barcode_decoder1_1 に送るよう、定義しています。
・ @app.route(“/barcode_decoder1_1”, …) としたところでは、クライアント側の HTML からバーコードの画像が送られてきたら、barcode_decoder1_1() 関数を実行します。barcode_decoder1_1() では、送られてきた画像を上記の barcode_decode1.py 内の decode1() 関数に入れることで、バーコードを読み取って、読み取った結果の文字列を取得します。
つぎに、バーコードの画像 image1 を base64 形式(image2)に変換します。
[1] バーコードの画像 image2 と、[2] ドラッグ&ドロップされたファイルのファイル名と、[3] バーコードを読み取った結果 str1 の3つは、原則、平易な文字列の形式となっています。
そこで、これら [1] ~ [3] の3つの文字列を “\n\n” で連結し、この文字列 str1 をクライアント側の HTML に返します。リターン2つ(”\n\n”)としたのは、特に理由はなく、たまたま、このようにしただけです。 (厳密にいえば、バーコードデータなどの中にもし、特殊文字 “\n\n” があると誤動作する可能性があるということになります。)
・ なお、コメントアウトしている @app.route(“/”) の部分は、サーバーのトップ画面を設定する場合の記載例です。トップ画面として使える HTML ファイルを作成されているようでしたら、このコメントアウトの部分を外して、トップ画面が表示されるようにしてください。
バーコード読み取り画面 barcode_decoder1.html の説明
・ バーコード読み取り画面 barcode_decoder1.html では、冒頭の <style> … </style> としたところで、ドラッグエリアの大きさや色を定義しています。
・ <body> … </body> 内で、アップロードに必要な各要素を定義しています。
具体的には、バーコードを読み取った結果などを表示するための要素 “div1″、”div2” と、ドロップするエリア “area1” と、サーバーから返ってきたバーコードの画像を表示するエリア “img1” の計4つを定義しています。
・ <script> … </script> 内で、ドラッグ中の処理と、ドロップ時の処理、ドロップ後にサーバーから応答が返ってきたときの処理を記載しています。
・ ドロップエリア “area1” において、addEventListener() を追加することで、領域内に入ったか、ドラッグしているところか、領域から離れたか、ドロップされたか、各イベントを監視してイベント発生時の処理を定義しています。
・ ファイルがドロップされると、addEventListener(“drop” … ) としたところで、function が実行されるようにしています。
この関数内で、ドロップされたときにもしファイルが見つかったら、その最初のファイル files1[0] について、request1() 関数を実行するようにしています。
・ request1() 関数では、ファイルを送信し、サーバーからの応答を取得する処理を記載しています。
サーバーとの送受信は、Fetch API という処理を使っています。典型的な記載としています。取得したファイルを data1 にぶら下げて送信をトライ(try)し、サーバーからの応答が返ってきたら(resp1.ok)、応答時の処理 response1() を実行します。
Fetch API を使うことで、画面遷移をせず、現在表示している画面を部分的に書き換えることで、受け取ったデータを表示させるようにしています。
・ response1() 関数では、サーバーから受け取った文字列を分けて、div1、div2、img1 の領域に表示しています。文字列の分割は app.py 側で “\n\n” とすることにしています。そこで、”\n\n” で split しています。この部分は、送受信したいデータの型や文字列などにより、自由に修正してください。
まとめ
バーコードの読み取りを行う Web アプリについて、スクリプトを作成しました。
以前に、バーコードを作成する Web アプリについてもスクリプトをまとめていますので、バーコードの作成から読み取りまで、Web アプリ上で自由自在になりました。
他にも、Flask や Raspberry Pi を用いたスクリプトを公開しています。もし、関心があるようでしたら、関連リンクなども参照してみてください。
なお、今回のスクリプトがちょっと難しいかなと思う方は、「Raspberry Pi でファイルアップローダ」なども参照してみてください。今回は、ファイルのアップローダとバーコードリーダを組み合わせた応用編になっています。いろいろと動かしているうちに要領がつかめると思います。
関連リンク
・ バーコードリーダー 【QRコード対応】
・ バーコード作成 Web アプリ 【Raspberry Pi & Flask】
・ Raspberry Pi でファイルアップローダ 【Flask】
・ Raspberry Pi で Flask を動かしてみる 【Python】
・ 初期設定のまとめ 【OpenCV & Raspberry Pi】
外部リンク [PR]
・ pyzbar · PyPI
・ python-barcode Getting started
・ Flask Development Server
・ Raspberry Pi 5 (8GB RAM) 技適対応品
・ Raspberry Pi 5 8GB 技適対応品/アクティブクーラー
・ Raspberry Pi 5 用 27W USB-C PD(電源アダプタ)
サンプルスクリプト
バーコード読み取りスクリプト barcode_decode1.py
import cv2
import sys
from pyzbar.pyzbar import decode
# import os
def decode1( image1 ):
image2 = cv2.cvtColor( image1, cv2.COLOR_RGBA2GRAY )
decode1 = decode( image2 )
str1 = ""
if len( decode1 ) > 0:
str1 = decode1[0].data.decode("utf-8")
for i1 in range( len(decode1)-1 ):
str1 = str1 + "\n" + decode1[i1+1].data.decode("utf-8")
return str1
if __name__ == "__main__":
# path1 = os.path.dirname( __file__ ) + "/"
# file1 = path1 + "barcode1.png"
file1 = sys.argv[1]
image1 = cv2.imread( file1 )
str1 = decode1( image1 )
print( str1 )
Web サーバー(Flask)用のスクリプト app.py
from flask import Flask, render_template, request
import os
from PIL import Image as im1
import sys
import cv2
import base64
import numpy as np1
sys.path.append( "/home/pi/barcode1" )
import barcode_decode1 # barcode_decode1.py
app = Flask(__name__)
# @app.route("/")
# def index1():
# return render_template( "index.html" )
def get_image1( file1 ):
image0 = file1.stream.read()
image1 = cv2.imdecode(np1.frombuffer(image0, np1.uint8), cv2.IMREAD_COLOR)
return image1
def get_image2( image1 ):
image2 = cv2.imencode('.png', image1)[1].tobytes()
image3 = base64.b64encode( image2 ).decode("utf-8") # A-Z, a-z, 0-9, +/, = 26+26+10+2 = 64
return image3
@app.route("/barcode_decoder1") # barcode decode
def barcode_decoder1():
return render_template("barcode_decoder1.html") # input type = "submit" -> request.files
@app.route("/barcode_decoder1_1", methods=["POST"])
def barcode_decoder1_1():
file1 = request.files["file1"]
image1 = get_image1( file1 )
str1 = barcode_decode1.decode1( image1 )
image2 = get_image2( image1 )
str1 = image2 + "\n\n" + file1.filename + "\n\n" + str1 + "\n\n"
return str1
バーコード読み取り用の画面の例 barcode_decoder1.html
<!DOCTYPE html>
<html lang="ja">
<head>
<title>barcode reader</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
#area1 {
width: 200px;
height: 70px;
border: 3px dashed #808080;
margin: 10px auto 10px 0;
text-align: center;
line-height: 18px;
background-color: #ffffff;
}
#area1.dragover1 {
border-color: #0000ff;
background-color: #cfdfef;
}
#img1 {
display: none;
}
#div1 {
color: #000000;
background-color: #ffffff;
width: 200px;
border: 1px;
border-color: #000000;
border-style: solid;
}
#div2 {
color: #efefef;
}
</style>
</head>
<body style="background-color:#efefef;">
<h1>barcode reader v0.1</h1>
<div id="div1"> </div>
<div id="area1"><br>ここにファイルを<br>ドラッグ&ドロップ</div>
</div>
<img id="img1" src="" alt="image"><br>
<div id="div2" ></div>
<script>
const area11 = document.getElementById( "area1" );
area11.addEventListener("dragenter", function( event1 ) {
event1.preventDefault();
area11.classList.add( "dragover1" );
});
area11.addEventListener("dragover", function( event1 ) {
event1.preventDefault();
});
area11.addEventListener("dragleave", function( event1 ) {
event1.preventDefault();
area11.classList.remove( "dragover1" );
});
area11.addEventListener("drop", function( event1 ) {
event1.preventDefault();
area11.classList.remove( "dragover1" );
const files1 = event1.dataTransfer.files;
if (files1.length > 0) {
request1( files1[0] );
}
});
async function request1( file1 ) { // Fetch API request & response
const url1 = "/barcode_decoder1_1";
const data1 = new FormData();
data1.append("file1", file1);
try {
const resp1 = await fetch(url1, { method: "POST", body: data1 });
if (resp1.ok) {
const str1 = await resp1.text();
response1(str1);
}
} catch ( error1 ) {console.error("Fetch error:", error1);}
}
function response1( str1 ) {
const a1 = str1.split("\n\n");
const img11 = document.getElementById("img1");
const div11 = document.getElementById("div1");
const div21 = document.getElementById("div2");
img11.src = "data:image/png;base64," + a1[0];
img11.style.display = "block";
div11.innerHTML = a1[2];
div21.innerHTML = a1[1];
}
</script>
</body>
</html>