Но как быть, если мы не хотим раскручивать чужие приложения? И встраивать к себе Activity и килограмм ресурсов из клиентской библиотеки ZXing не хочется... Выход есть. Мы можем самостоятельно реализовать всё что нам нужно, используя только ZXing core библиотеку. Давайте посмотрим, как это сделать.
Вначале нам нужно просто получить изображение с камеры. При чём делать это мы будем не по клику как в примере работы с камерой, а непрерывно. Для этого наше Activity должно интерфейс Camera.PreviewCallback и в методе onPreviewFrame мы сможем работать с preview-картинками в реальном времени. Чтобы распознавание кода не привело к тормозам в интерфейсе, каждую полученную картинку будем обрабатывать в отдельном потоке. Тот поток, который успешно распознал код, должен вызывать переход на другое Activity, где и будем отображать полученную из QR-кода строку. Если несколько потоков распознают картинку, отображать результат должен только первый. Для этого используем синхронизацию. Вот код нашего Activity:
import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.hardware.Camera; import android.hardware.Camera.Size; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import com.google.zxing.*; import com.google.zxing.common.HybridBinarizer; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Vector; public class QKActivity extends Activity implements SurfaceHolder.Callback, Camera.PreviewCallback, Camera.AutoFocusCallback { private Camera camera; private SurfaceView preview; private ViewfinderView vfv; private Result rawResult; private String TAG = QKActivity.class.getSimpleName(); private long currKey; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); FrameLayout fl = new FrameLayout(this); preview = new SurfaceView(this); SurfaceHolder surfaceHolder = preview.getHolder(); surfaceHolder.addCallback(this); fl.addView(preview, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); vfv = new ViewfinderView(this, null); fl.addView(vfv, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); setContentView(fl); } @Override protected void onResume() { super.onResume(); camera = Camera.open(); vfv.setCamera(camera); currKey = System.currentTimeMillis(); } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); camera = null; } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceCreated(SurfaceHolder holder) { try { camera.setPreviewDisplay(holder); camera.setPreviewCallback(this); } catch (IOException e) { e.printStackTrace(); } Size previewSize = camera.getParameters().getPreviewSize(); float aspect = (float) previewSize.width / previewSize.height; int previewSurfaceWidth = preview.getWidth(); LayoutParams lp = preview.getLayoutParams(); Camera.Parameters parameters = camera.getParameters(); parameters.set("orientation", "landscape"); camera.setParameters(parameters); lp.width = previewSurfaceWidth; lp.height = (int) (previewSurfaceWidth / aspect); preview.setLayoutParams(lp); try { camera.autoFocus(this); } catch (Exception e) { e.printStackTrace(); } camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void onPreviewFrame(final byte[] bytes, final Camera camera) { new Recognizer(currKey, bytes).start(); } @Override public void onAutoFocus(boolean b, Camera cam) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if (camera!=null && (Camera.Parameters.FOCUS_MODE_AUTO.equals(camera.getParameters().getFocusMode()) || Camera.Parameters.FOCUS_MODE_MACRO.equals(camera.getParameters().getFocusMode()))) { camera.autoFocus(QKActivity.this); } } }).start(); } public class Recognizer extends Thread { private long key; private byte[] bytes; public Recognizer(long key, byte[] bytes) { this.key = key; this.bytes = bytes; } @Override public void run() { try { Size previewSize = camera.getParameters().getPreviewSize(); Rect rect = vfv.getFramingRectInPreview(); LuminanceSource source = new PlanarYUVLuminanceSource(bytes, previewSize.width, previewSize.height, rect.left, rect.top, rect.width(), rect.height(), false); Map<DecodeHintType,Object> hints = new HashMap<DecodeHintType, Object>(); Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>(1); decodeFormats.add(BarcodeFormat.QR_CODE); hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, new ResultPointCallback() { @Override public void foundPossibleResultPoint(ResultPoint resultPoint) { vfv.addPossibleResultPoint(resultPoint); } }); MultiFormatReader mfr = new MultiFormatReader(); mfr.setHints(hints); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); rawResult = mfr.decodeWithState(bitmap); if (rawResult!=null) { Log.e(TAG, rawResult.getText()+" key="+key+" currKey="+currKey); if (key==currKey) { currKey = System.currentTimeMillis(); runOnUiThread(new Runnable() { @Override public void run() { Intent i = new Intent(QKActivity.this, ResultActivity.class); i.putExtra(ResultActivity.RESULT, rawResult.getText()); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); } }); } } } catch (Exception e) { e.printStackTrace(); } } } }
Ещё одна "хитрость": мы выполняем автофокусировку камеры каждые 2 секунды, что позволяет улучшить "прицеливание".
Полный набор исходников в виде рабочего проекта доступен на github.
UPD (2.10.2013): Проект обновлён, если у вас были проблемы с распознаванием или с повторным распознаванием после отображения результата, проверьте. И, кстати, спасибо всем за замечания.
Учусь, пишу:
ОтветитьУдалитьLuminanceSource source = new LuminanceSource(w,h);
получаю ошибку "Cannot instantiate the type LuminanceSource".
Что я делаю не так? Как-то не так подключил zxing?
где рыть?
А у меня получилось запустить но код не определяет просто запускается камера и все....
ОтветитьУдалитьбыло бы хорошо иметь готовые проекты того что вы делаете. т.е скачивание рабочего примера. Это поможет таким новичкам как я
ОтветитьУдалитьСпасибо за Ваши публикации, очень полезно и интересно. У меня такая же проблема с LuminanceSourceImpl компилятор пишет cannot be resolved to a type. Ответьте, пожалуйста, очень нужно.
ОтветитьУдалитьСпасибо.
Этот комментарий был удален администратором блога.
ОтветитьУдалитьЗагрузил, все работает. Автору спасибо.
ОтветитьУдалитьГоспода, у кого не работает - возможно, Вы не подключили к проекту библиотеку Zxing?
В Eclipse подключал вот так:
Project -> Propertires -> Java Build Path -> Add external jars -> core.jar из zxing
Вопрос: как перенастроить на распознование кодов EAN-13?
А как перделать его впортретный и не поноэкранный?
ОтветитьУдалитьУйти от полноэкранного варианта удалось,но вот к портретному виду никак.
ОтветитьУдалитьчто нужно изменить в коде, чтоб в портретном виде работало?
не работает, мучаюсь, приложение вылетает и закрывается(
ОтветитьУдалитьПриложение работает, только когда проверяю на своем устройстве (HTC ONE V) сканирование происходит только один раз, то есть если вернутся обратно в камеру, повторного сканирования не будет. И точки не на том месте где нужно.
ОтветитьУдалитьИсправлено.
УдалитьКруто, спасибо!
УдалитьЗапустилось норм но не распознает ни один qr код....
ОтветитьУдалитьПосле того как закомментил 3 строчки:
ОтветитьУдалить// if (left + width > dataWidth || top + height > dataHeight) {
// throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
// }
приложение стало сканировать и распознавать. Но только один код. Потом надо перезапускать. Картина в точности как у Артема.
Исправлено
УдалитьОшибка в автофокусе! Сначала нужно запустить превью, а затем вызывать автофокус:
ОтветитьУдалитьcamera.startPreview();
{ try
{
camera.autoFocus(this);
}
catch (Exception e)
{
e.printStackTrace();
}
Спасибо, не мог понять почему автофокус не работает
УдалитьПочему на Adndoid 2.3 не работает, приложение сразу падает ((( А вот на Adndoid 4.1.2 работает хорошо
ОтветитьУдалитьМожет знает кто-нибудь, как исправить?
ОтветитьУдалитьПопробуйте в методе onCreate добавить строку surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
УдалитьЕще одна ошибка при работе с камерой под Android 2.1 getClientFromCookie: client appears to have died Никто не в курсе что это может означать?
ОтветитьУдалитьSamsung p3100 при распозновании нужно чтобы изображение попадало в правый нижний угол, только тогда нормально распознается. Желтые точки ресуются со смещением от реалной точки примерно -50px по X и Y.
ОтветитьУдалитькамера запускается и все...точки ставит не в тех местах...не распознает:( если кто-то решил проблему и все нормально работает, то пришлите плиз исходники, очень надо!
ОтветитьУдалитьтолько если в правом нижнем углу код- тогда распознает
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьНормально работает в эмуляторе Genymotion. Вот только нужно добавить в манифест после этого:
ОтветитьУдалитьuses-permission android:name="android.permission.CAMERA"
вот это:
uses-feature android:name="android.hardware.camera"
uses-feature android:name="android.hardware.camera.autofocus"
Соответственно в скобочках) Тут удаляет(
И мне кажется, что библиотека zbar быстрее работает. Правда там нет этих точек(в странных местах, которые в принципе можно закоментить тут) и осветления экрана и прямоугольной области по средине... =) Наверно можно вместе соединить.
Почему то не распознаёт
ОтветитьУдалитьНе фокусируется камера. Точки желтые бегают но ничего не происходит
ОтветитьУдалитьне могу разобратся с библиоткой Camera. вызываю библиотеку а оно вычеркнута
ОтветитьУдалитьа если камера на устройстве без автофокусировки, будет ли работать программа или сразу вылетит??
ОтветитьУдалить