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