Jython+zxing+Processingでバーコードリーダ/ライタ

GitHub - zxing/zxing: ZXing ("Zebra Crossing") barcode scanning library for Java, Androidに割と使い易そうなバーコードリーダ/ライタライブラリがあったので、使ってみた。

少し前からメインマシンになったMacbook ProにはiSightが付いている。解像度の低いWebカメラも持っている。これらのカメラをバーコード読み取り器に使うのが良いだろう。となると、ソフトウェアからiSightなりWebカメラなりのカメラ画像を読み取る必要がある。Objective-Cで書けばなんとかなるが、zxingはJava用ライブラリなので、JVMから操作出来なければいけない。JNIやJNAを使うコードを書くのも面倒なので、Processingの processing.video ライブラリを採用する。ついでに、ソフトウェアの画像表示などもProcessingに任せることにした。

videoライブラリを使うからといって、Processingで全部書いてしまうと、後々使いにくい。Jythonで processing.core を使えば、Jythonプログラム内でProcessingコードが書ける。今回は、拡張性も考えてそのやり方を採用した。

ソフトウェアのコードは一番最後に掲載するとして、とりあえず完成したもののスクリーンショット

真ん中付近の赤い枠内にある画像を解析対象の画像とし、発見したバーコードの端は緑丸で示される。バーコード解析完了と同時に、画面左上にバーコード種類とその内容を表示する。

swingのGUIも使って、バーコードの内容はテキストフィールドにも表示させるなどした。

読み書きに対応する(バー)コードはzxingが対応する種類全てである。このソフトウェアの左下部コンボボックスを開くと分かる。実は知らないコード種類があって、これから勉強していこうと思っている。ライブラリに任せると知らなくても出来てしまう。

ソフトウェア下部で、コード種類を選択、内容をテキストフィールドに入力してconvertボタンを押すと、選択種類に対応したコード画像を生成する。

画像は、Processingのopen(FILE)命令で、ソフトウェアを動かしている環境の設定に依存して、PNGファイルを表示するためのソフトウェアが起動する。

ただバーコードリーダでバーコードを読んでいても面白くない。この応用としては、あるバーコードを読み取ったときに、Twitterにメッセージをポストする、や、Arduinoに何かさせる、などが考えられる。身近に溢れているバーコードに、このソフトウェアで意味付けをして、色々遊んでみるのが楽しいだろう。

ソフトウェア実行時には、Processingのcore.jar/video.jar、zxingのcore.jar/javase.jar などをCLASSPATHに追加しておく必要がある。

WindowsでもProcessing.videoが動くならおそらくソフトウェアも動作する。が、バーコード読み取り結果を表示するためのフォントとしてOsakaを指定しているので、変更が必要だろう。

ソフトウェアのコードを掲載。

#!/opt/usr/bin/jython

#   Copyright (C) SAEKI Yoshiyasu

from processing.core import PApplet, PImage
from processing.video import Capture
from javax.swing import JFrame, JPanel, JTextField, JComboBox, JButton, BoxLayout
# from java.awt import FlowLayout

from com.google.zxing import MultiFormatReader, MultiFormatWriter, \
    BinaryBitmap, DecodeHintType, BarcodeFormat
# from com.google.zxing.oned import EAN13Reader
from com.google.zxing.common import HybridBinarizer
from com.google.zxing.client.j2se import BufferedImageLuminanceSource, MatrixToImageWriter
# from javax.imageio import ImageIO
from java.util import Hashtable
from java.io import File
import re

WIN_W = 640
WIN_H = 480
RECT_W = 300
RECT_H = 200

brpanel = None   # Processing#PApplet
format_cb = None # JComboBox
code_tf = None   # JTextField

## Processing

class BarcodeReaderPanel(PApplet):
    def __init__(self):
        self.mC = None
        pass
    def setup(self):
        self.size(WIN_W, WIN_H)
        self.background(255)
        self.frameRate(12)
        self.mC = Capture(self, self.width, self.height, 12)
    def draw(self):
        if self.mC.available():
            self.mC.read()
            self.image(self.mC, 0, 0)
            self.rectMode(self.CENTER)
            self.noFill()
            self.strokeWeight(5)
            self.stroke(255, 0, 0, 100)
            self.rect(self.width/2, self.height/2, 300, 200)
            img = self.mC.getImage()  # BufferedImage
            result_dict = getCode(img)
            if result_dict["result"] == True:
                for point in result_dict["point"]:
                    p_x = point.getX() + self.width/2-RECT_W/2
                    p_y = point.getY() + self.height/2-RECT_H/2
                    self.ellipseMode(self.CENTER)
                    self.stroke(0, 255, 0, 100)
                    self.ellipse(p_x, p_y, 10, 10)
                self.textFont(self.createFont("Osaka", 24))
                self.fill(0, 100, 100, 255)
                self.text(result_dict["format"], 20, 30)
                self.text(result_dict["text"], 20, 58)
                self.fill(200, 200, 200, 255)
                self.text(result_dict["format"], 22, 32)
                self.text(result_dict["text"], 22, 60)
                format_cb.setSelectedItem(result_dict["format"])
                code_tf.setText(result_dict["text"])
                print result_dict["format"], result_dict["text"]
                self.noLoop()
    def keyPressed(self, e):
        # code_tf.setText("000")
        self.loop()
    def mousePressed(self, e):
        # self.mC.settings()
        pass

## zxing

def getCode(img):
    reader = MultiFormatReader()
    # reader = EAN13Reader()
    # ImageIO.write(img, "PNG", open("hogehoge.png",'w'))
    # readImage = ImageIO.read(open("hogehoge.png"))
    readImage = img
    source = BufferedImageLuminanceSource(readImage,
        WIN_W/2-RECT_W/2, WIN_H/2-RECT_H/2, RECT_W, RECT_H)
    bitmap = BinaryBitmap(HybridBinarizer(source))
    hints = Hashtable()
    hints.put(DecodeHintType.TRY_HARDER, True)
    result_dict = {"result":False, "text":"", "format":"", "point":[]}
    try:
        result = reader.decode(bitmap, hints)
        result_dict["result"] = True
        result_dict["text"] = result.getText()
        result_dict["format"] = result.getBarcodeFormat().getName()
        result_dict["point"] = result.getResultPoints()  # [ResultPoint]
    except:
        print "Not found"
    return result_dict

def getFormat(format):
    bf = None
    if format == "CODE_128":
        bf = BarcodeFormat.CODE_128
    elif format == "CODE_39":
        bf = BarcodeFormat.CODE_39
    elif format == "DATAMATRIX":
        bf = BarcodeFormat.DATAMATRIX
    elif format == "EAN_13":
        bf = BarcodeFormat.EAN_13
    elif format == "EAN_8":
        bf = BarcodeFormat.EAN_8
    elif format == "ITF":
        bf = BarcodeFormat.ITF
    elif format == "PDF417":
        bf = BarcodeFormat.PDF417
    elif format == "QR_CODE":
        bf = BarcodeFormat.QR_CODE
    elif format == "RSS14":
        bf = BarcodeFormat.RSS14
    elif format == "UPC_A":
        bf = BarcodeFormat.UPC_A
    elif format == "UPC_E":
        bf = BarcodeFormat.UPC_E
    return bf

def convertCode(e):
    format = format_cb.getSelectedItem()
    text = code_tf.getText()
    writer = MultiFormatWriter()
    bm = writer.encode(text.encode("UTF-8"), getFormat(format),
                       RECT_W, RECT_H)
    filename = format + "-" + re.sub("[<>:*?\"/\\|]", "_", text) + ".png"
    MatrixToImageWriter.writeToFile(bm, "PNG", File(filename))
    while (not File(filename).exists()):
        brpanel.delay(2)
    brpanel.open(filename)
    # codeimg = brpanel.loadImage(filename, "png")
    # brpanel.background(255)
    # brpanel.image(codeimg, WIN_W/2-RECT_W/2, WIN_H/2-RECT_H/2)
    # brpanel.noLoop()
    print "created " + filename


## main

mainframe = JFrame(title="BarcodeReader", resizable=0,
    defaultCloseOperation=JFrame.EXIT_ON_CLOSE)

frame = JPanel()
frame.setLayout(BoxLayout(frame, BoxLayout.Y_AXIS))

brpanel = BarcodeReaderPanel()
frame.add(brpanel)
brpanel.init()
while brpanel.defaultSize and not brpanel.finished:
    pass

msg_frame = JPanel()
msg_frame.setLayout(BoxLayout(msg_frame, BoxLayout.X_AXIS))

format_cb = JComboBox([
        "CODE_128", "CODE_39", "DATAMATRIX", "EAN_13", "EAN_8",
        "ITF", "PDF417", "QR_CODE", "RSS14", "UPC_A", "UPC_E"])
format_cb.setSelectedItem("EAN_13")
msg_frame.add(format_cb)

code_tf = JTextField()
code_tf.setText("000")
msg_frame.add(code_tf)

convert_button = JButton("convert", actionPerformed=convertCode)
msg_frame.add(convert_button)

frame.add(msg_frame)

mainframe.add(frame)
mainframe.pack()
mainframe.visible = 1