バーコードリーダ Web アプリ 【Raspberry Pi & Flask】

Raspberry Pi

ローカルネットワーク上の Raspberry Pi を使って、バーコードリーダの Web アプリを作ってみます。
以下の環境で動作確認をしています。
環境:
・ Raspberry Pi (bullseye、サーバーとして使用、OpenCV をインストール済み)
・ Windows パソコン(クライアントとして使用)
・ Wi-Fi ネットワーク(Raspberry Pi と Windows パソコンはネットワークに接続済み)

背景 ~ バーコードリーダの Web アプリ化!

以前に、ローカルパソコンの Python を使って、QR コードなどの読み取りができるバーコードリーダについて、スクリプトをまとめています。パソコンに USB カメラを接続すると、バーコードリーダとして活用できます。

そこで今回は、バーコードリーダの 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 サーバーの起動・停止

⑧ 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 アプリでできることになります。

他にも、Flask や Raspberry Pi を用いたスクリプトなどについて、このサイトでまとめています。もし、関心があるようでしたら、関連リンクなども参照してみてください。

関連リンク
・ バーコード作成 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>

 

タイトルとURLをコピーしました