アンドロイドでバーコード読み取り
ZXing(ゼブラ・クロッシング)を使って、バーコード読み取り
ZXing(ゼブラ・クロッシング)過去バージョンのダウンロード(old version)
SATCHを使って、バーコード読み取り
SATCHの一次元と二次元のコード・サンプルを統合
SATCH側でオートフォーカス設定
TOP
現在対応しているフォーマットは以下の通り
UPC-A and UPC-E
EAN-8 and EAN-13
Code 39
Code 93
Code 128
ITF
Codabar
RSS-14 (all variants)
QR Code
Data Matrix
Aztec ('beta' quality)
PDF 417 ('alpha' quality)
ZXing(ゼブラ・クロッシング)はここからダウンロード
現在のバージョンは2.0(2012/07/28) 現在のバージョンは2.2(2013/09/13)
以下の記述は2.0をターゲットにしています。2.0が必要な方はここからDLしてください。
Google Playで配布されている「QRコードスキャナー」もこのライブラリーの製造元が作ってます。
作り方
以前はantでリビルドする工程があったようですが、現在は極シンプルです。
ダウンロードして解凍したもので、プロジェクトに必要なのは以下の3つ。
●coreフォルダーの中のcore.jar
●javaseフォルダーの中のjavase.jar
●android/src/com/google/zxing/client/androidフォルダーの中のPlanarYUVLuminanceSource.java
main.xmlを記述する際に参考になるのが、android/res/layoutフォルダーの中のcapture.xml
では、Eclipseを起動してプロジェクトを作成してみます。
File -> New -> Otherで新規プロジェクト作成。
ついで、新しいパッケージも作っておきます。
File -> New -> Packageで名前は、「com.google.zxing.client.android」です。
プロジェクトフォルダーのsrc/com/google/zxing/client/androidフォルダーにPlanarYUVLuminanceSource.javaをコピー。
さらに、プロジェクトフォルダーにlibsという新規フォルダーを作成して、core.jarとjavase.jarをコピー。
Eclipseに戻って、プロジェクトをRefresh。
念のため、プロジェクトのPropertiesで、ResourceのText file encodingをUTF-8にしておく。
libsのcore.jarとjavase.jarのBuild pathをとおしておく。
これでプロジェクトのベースは完了。後はコーディングです。
【AndroidManifest.xml】
画面の向きをLandscapeにして、cameraを設定しておきます。
<!-- 略 --> <activity android:name=".MainActivity" android:label="@string/title_activity_main" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 略 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- オートフォーカスを設定します --> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 略 -->
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/preview_view" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <View android:id="@+id/viewfinder_view" android:layout_width="400dip" android:layout_height="200dip" android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:background="#55ff6666" /> </RelativeLayout> </FrameLayout>
package com.google.zxing.client.xxxxx; import java.io.IOException; import com.google.zxing.client.android.PlanarYUVLuminanceSource; import com.google.zxing.BinaryBitmap; import com.google.zxing.MultiFormatReader; import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Point; import android.hardware.Camera; import android.os.Bundle; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; import android.view.KeyEvent; public class MainActivity extends Activity implements SurfaceHolder.Callback, Camera.PreviewCallback, Camera.AutoFocusCallback { private static final String TAG = "ZXingBase"; private static final int MIN_PREVIEW_PIXCELS = 320 * 240; private static final int MAX_PREVIEW_PIXCELS = 800 * 480; private Camera myCamera; private SurfaceView surfaceView; private Boolean hasSurface; private Boolean initialized; private Point screenPoint; private Point previewPoint; private static final int DIALOG_EXIT = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); hasSurface = false; initialized = false; setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); surfaceView = (SurfaceView)findViewById(R.id.preview_view); SurfaceHolder holder = surfaceView.getHolder(); if (hasSurface) { initCamera(holder); } else { holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } } @Override protected void onPause() { closeCamera(); if (!hasSurface) { SurfaceHolder holder = surfaceView.getHolder(); holder.removeCallback(this); } super.onPause(); } @Override public void surfaceCreated(SurfaceHolder holder) { if (!hasSurface) { hasSurface = true; initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void onAutoFocus(boolean success, Camera camera) { if (success) camera.setOneShotPreviewCallback(this); } @Override public boolean onKeyDown(int keyCode, KeyEvent msg){ switch(keyCode){ case android.view.KeyEvent.KEYCODE_MENU: case android.view.KeyEvent.KEYCODE_CAMERA: if (myCamera != null) { Camera.Parameters parameters = myCamera.getParameters(); if (!parameters.getFocusMode().equals(Camera.Parameters.FOCUS_MODE_FIXED)) { myCamera.autoFocus(this); } } return true; case android.view.KeyEvent.KEYCODE_BACK : showDialog( DIALOG_EXIT ); return true; } return false; } /** Camera.PreviewCallback */ @Override public void onPreviewFrame(byte[] data, Camera camera) { View finderView = (View)findViewById(R.id.viewfinder_view); int left = finderView.getLeft() * previewPoint.x / screenPoint.x; int top = finderView.getTop() * previewPoint.y / screenPoint.y; int width = finderView.getWidth() * previewPoint.x / screenPoint.x; int height = finderView.getHeight() * previewPoint.y / screenPoint.y; PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data,previewPoint.x,previewPoint.y,left,top,width,height,false); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); MultiFormatReader reader = new MultiFormatReader(); try { Result result = reader.decode(bitmap); Toast.makeText(this, result.getText(), Toast.LENGTH_LONG).show(); } catch (Exception e) { //Toast.makeText(this, "error: " + e.getMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(this, "読み取れませんでした: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } //--------------------------------------------------------------------------------- private void initCamera(SurfaceHolder holder) { try { openCamera(holder); } catch (Exception e) { Log.w(TAG, e); } } private void openCamera(SurfaceHolder holder) throws IOException { if (myCamera == null) { myCamera = Camera.open(); if (myCamera == null) { throw new IOException(); } } myCamera.setPreviewDisplay(holder); if (!initialized) { initialized = true; initFromCameraParameters(myCamera); } setCameraParameters(myCamera); myCamera.startPreview(); } private void closeCamera() { if (myCamera != null) { myCamera.stopPreview(); myCamera.release(); myCamera = null; } } private void setCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(previewPoint.x, previewPoint.y); camera.setParameters(parameters); } private void initFromCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); WindowManager manager = (WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); if (width < height) { int tmp = width; width = height; height = tmp; } screenPoint = new Point(width, height); Log.d(TAG, "screenPoint = " + screenPoint); previewPoint = findPreviewPoint(parameters, screenPoint, false); Log.d(TAG, "previewPoint = " + previewPoint); } private Point findPreviewPoint(Camera.Parameters parameters, Point screenPoint, boolean portrait) { Point previewPoint = null; int diff = Integer.MAX_VALUE; for (Camera.Size supportPreviewSize : parameters.getSupportedPreviewSizes()) { int pixels = supportPreviewSize.width * supportPreviewSize.height; if (pixels < MIN_PREVIEW_PIXCELS || pixels > MAX_PREVIEW_PIXCELS) { continue; } int supportedWidth = portrait ? supportPreviewSize.height : supportPreviewSize.width; int supportedHeight = portrait ? supportPreviewSize.width : supportPreviewSize.height; int newDiff = Math.abs(screenPoint.x * supportedHeight - supportedWidth * screenPoint.y); if (newDiff == 0) { previewPoint = new Point(supportedWidth, supportedHeight); break; } if (newDiff < diff) { previewPoint = new Point(supportedWidth, supportedHeight); diff = newDiff; } } if (previewPoint == null) { Camera.Size defaultPreviewSize = parameters.getPreviewSize(); previewPoint = new Point(defaultPreviewSize.width, defaultPreviewSize.height); } return previewPoint; } //----------------------------- protected Dialog onCreateDialog(int id) { Dialog dialog; switch(id) { case DIALOG_EXIT: { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage( "Really want to quit the application?" ) .setCancelable(true) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); }}) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); }}); dialog = builder.create(); break; } default: dialog = null; } return dialog; } }
ZXing 2.0
ZXing 2.1
ZXing 2.2
もっと古いバージョンはこちらから
TOP
SATCHはAR(拡張現実)アプリ作成用のSDKですが、トラッキング用のマーカー画像をいちいち作るのが面倒
な場合、バーコードで代用すればいいんじゃないか?という程度の発想です。
これはIKEAのカタログARのまねっこをしてみようという思いつきの前段です。
(ほんちゃんのLuaはQRコード読み取りと、画像トラッキング・3Dオブジェクト表示の統合コードになります。現在、QRコード読み取りと画像トラッキングを単一コードで実行するまでの動作確認の検証は終了してますんで、もう少々お待ちください。)
以下は、一次元バーコードのコードですが、二次元(QRコード)の場合も、ほぼ同様です。
ただ、二次元のコードには、local visor = Overlay(getCurrentScene():getObjectByName("BarCodeVisor"))のコードが無いので、追加してください。
一次元バーコード・リーダー
二次元バーコード・リーダー
SATCHのシナリオは、SATCH Developers siteの"TESTS"サンプルの中の「testBarCodePlugin」と「testBarCodePlugin2D」を使います。
独自アプリを作りたい場合は、dpdファイルの中のapplicationidを書き換える必要があります。
ここでは、Javaとの通信をやる必要があったので、以下のようにluaにコードを追加しています。
【Lua】
-- initialize the linear visor position
local lOS = getOSType()
--if (lOS == TI_OS_IPHONEOS) then
local visor = Overlay(getCurrentScene():getObjectByName("BarCodeVisor"))
--visor:setRotate(1.57)
--end
-- initialize the barcode reader
local text = Text2D(getCurrentScene():getObjectByName("text"))
local componentInterface = getComponentInterface()
local lBarCodePlugin = nil
local bIsOK = true -- flag reflecting if all OK or something wrong
local eCode = 0 -- return error code variable
local vidcap_width = 0
local vidcap_height = 0
local prev_code = ""
local tRequestedCodes = {
"UPCA" ,
"EAN13" ,
"UPCE" ,
"EAN8" ,
"CODE39" ,
"CODE128" ,
"ITF" ,
-- etc. etc.
}
-- products cross-table
Products_CrossTable = {
-- put here your products, in the following form:
-- [ "UPCA;123456789012" ] = "Toto" ,
-- [ "EAN13;1234567890123" ] = "Tata" ,
}
-- helper handle-result function
function HR(in_Bool, in_sMessage)
local res = true
if ( not in_Bool ) then
text:setText(in_sMessage)
res = false
end
return res
end
-- initialize the text output
text:setText("")
text:setVisible(true)
-- determine if the runtime has the BarCodePluginManager
if (bIsOK) then
bIsOK = HR( getBarCodePluginManager, "ERROR : getBarCodePluginManager not disponible" )
end
if (bIsOK) then
lBarCodePlugin = getBarCodePluginManager()
bIsOK = HR ( lBarCodePlugin, "ERROR : BarCodePlugin instance is nil" )
end
-- link the BarCode plugin to the videocapture (use it coupled to the given videocapture)
if (bIsOK) then
eCode = lBarCodePlugin:linkToVideoCapture( getCurrentScene():getObjectByName("vidCap") )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : could not link to videocapture" )
end
if (bIsOK) then
repeat
eCode, vidcap_width, vidcap_height = lBarCodePlugin:getLinkedVideoCaptureDimensions( )
-- eCode can be signaled unitl we receive 1st frame
if ( HR( 0 == eCode, "ERROR : BarCodePlugin : could not get videocapture dims" ) ) then
text:setText("") -- back to normal
end
coroutine.yield()
until ( 0 == eCode )
end
-- decide Region Of Interest based on the videocapture frame dimensions
if (bIsOK) then
-- whole image
--local roiX = 0
--local roiY = 0
--local roiDX = vidcap_width
--local roiDY = vidcap_height
-- only the center part
local strip_dx = vidcap_width
local strip_dy = 40
--
local roiX = ( vidcap_width - strip_dx ) / 2
local roiY = ( vidcap_height - strip_dy ) / 2
local roiDX = strip_dx
local roiDY = strip_dy
--
eCode = lBarCodePlugin:setRegionOfInterest( roiX, roiY, roiDX, roiDY )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : could not set ROI" )
end
-- decide if the required code(s) is (are) within the list of supported codes
local tSup -- variable to store supported codes
if (bIsOK) then
eCode, tSup = lBarCodePlugin:getSupportedCodes( )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : could not get supported codes" )
if (bIsOK) then -- only for info : log supported codes
--LOG ("INFO : BarCode plugin supports:")
for k2,codeSup in pairs(tSup) do
--LOG (" " .. codeSup)
end
end
end
if (bIsOK) then
--LOG("INFO : Determine if required codes are supported")
for k,codeReq in pairs(tRequestedCodes) do
--LOG("Required = " .. codeReq)
local bFound = false;
for k2,codeSup in pairs(tSup) do
if (codeReq == codeSup) then
--LOG (" found!")
bFound = true
break
end
end
bIsOK = HR( bFound, "BarCodePluginManager : no detector for : " .. codeReq )
if (not bIsOK) then
--LOG (" ERROR : not found!")
break
end
end
end
-- set the BarCodes we're searching for
if (bIsOK) then
eCode = lBarCodePlugin:setRequestedCodes( tRequestedCodes )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : could not set requested codes" )
end
-- init done, ready for main loop
coroutine.yield()
if (bIsOK) then
local i = 0
repeat
i = i + 1
--text:setText("out")
--LOG("out")
eCode, result = lBarCodePlugin:detect( )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : on detect()" )
if (bIsOK) then
for k,v in pairs(result) do
local ct = Products_CrossTable[ v ]
if ( not (nil == ct) ) then
--text:setText( ct )
else -- not stored in our data base
if prev_code ~= v then
--if (visor:getVisible()) then
--LOG("visible")
text:setText(v)
LOG("found")
visor:setVisible(false)
componentInterface:executeAppFunc("setTrackingStatus",v)
prev_code = v
--end
end
end
end
end
isCommand, command = componentInterface:pullCommand()
if isCommand then
if command["CommandName"] == "visor" then
--
--componentInterface:executeAppFunc("sendLog","receive command")
local s_type = command["arg0"]
if s_type == "show" then
visor:setVisible(true)
prev_code = ""
text:setText("")
elseif s_type == "hide" then
visor:setVisible(false)
end
end
end
until coroutine.yield()
end
-- end of file --
<platform target="windows"> <camera name="camera" position="{0.000000, 0.000000, 75.000000}" orientation="{1.000000, 0.000000, 0.000000, 0.000000}" visible="true" rendergroup="50" scale="{1.000000, 1.000000, 1.000000}" visibilitymask="-1" fovy="0.785398" nearclip="1.000000" farclip="10000.000000" aspectratio="1.333333" /> </platform> <platform target="android"> <camera name="camera" position="{0.000000, 0.000000, 75.000000}" orientation="{1.000000, 0.000000, 0.000000, 0.000000}" visible="true" rendergroup="50" scale="{1.000000, 1.000000, 1.000000}" visibilitymask="-1" fovy="0.785398" nearclip="1.000000" farclip="10000.000000" aspectratio="1.333333" /> </platform> <platform target="iPhoneOS"> <camera name="camera" position="{0.000000, 0.000000, 75.000000}" orientation="{1.000000, 0.000000, 0.000000, 0.000000}" visible="true" rendergroup="50" scale="{1.000000, 1.000000, 1.000000}" visibilitymask="-1" fovy="0.785398" nearclip="1.000000" farclip="10000.000000" aspectratio="1.333333" /> </platform> <platform target="macOS"> <camera name="camera" position="{0.000000, 0.000000, 75.000000}" orientation="{1.000000, 0.000000, 0.000000, 0.000000}" visible="true" rendergroup="50" scale="{1.000000, 1.000000, 1.000000}" visibilitymask="-1" fovy="0.785398" nearclip="1.000000" farclip="10000.000000" aspectratio="1.333333" /> </platform>
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg){
switch(keyCode){
case android.view.KeyEvent.KEYCODE_BACK :
showDialog( DIALOG_EXIT );
return true;
case android.view.KeyEvent.KEYCODE_CAMERA :
case android.view.KeyEvent.KEYCODE_MENU :
String[] args = new String[1];
args[0] = "show";
_kddiComponent.enqueueCommand("visor", args);
return true;
}
return false;
}
SATCHの一次元バーコード読み取りとQRコード読み取りのサンプルが分かれていたので1つにしてみました。
といっても、そんな難しい話じゃないんですが.....。
ベースは2次元(QRコード)読み取りのサンプルです。
Visorは四角形のやつで、読み取り範囲も大きな矩形範囲を読み取るってことにしときます。
コードの以下の部分はそのままにしときます。
---------------------------------------------------------
-- decide Region Of Interest based on the videocapture frame dimensions
if (bIsOK) then
-- whole image
-- only the center part
local rect_dx = vidcap_width - 40
local rect_dy = vidcap_height - 40
--
local roiX = ( vidcap_width - rect_dx ) / 2
local roiY = ( vidcap_height - rect_dy ) / 2
local roiDX = rect_dx
local roiDY = rect_dy
--
eCode = lBarCodePlugin:setRegionOfInterest( roiX, roiY, roiDX, roiDY )
bIsOK = HR( 0 == eCode, "ERROR : BarCodePlugin : could not set ROI" )
end
local tRequestedCodes = {
"QRCODE" ,
"UPCA" ,
"EAN13" ,
"UPCE" ,
"EAN8" ,
"CODE39" ,
"CODE128" ,
"ITF" ,
-- etc. etc.
}
オートフォーカスの設定はわりと簡単。
使うのは、videoConfig_android_backcam_320x240_15fps.xmlファイルです。
サンプルTESTSのtestFocusVideoにあります。
こんなファイル
【SATCH Studio】
cameraを選択。
まず、Calibration fileにvideoConfig_webcam_640x480_15fps.xmlが読み込まれているのを確認。
Alternative platformにチェックを入れて、androidの欄に、videoConfig_android_backcam_320x240_15fps.xmlを入力して、
Set initial valuesをクリック。
以上です。
こんな感じ。
画面をタッチすることで、オートフォーカスが起動します。
インストール
アプリをQRコードからインストールする方法はコチラを参照
TOP