[5]OpenCV4Android機器視覺應用入門-拍照預覽界面動態繪圖

OpenCV4Android機器視覺應用入門-引言 - 王傳軍的文章 - 知乎專欄

[1]OpenCV4Android機器視覺應用入門-JNI編寫HelloWorld - 王傳軍的文章 - 知乎專欄

[2]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法1:純java層開發 - 王傳軍的文章 - 知乎專欄

[3]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法2:java和native混編 - 夜話安卓 - 知乎專欄

[4]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法3:純native方法

阿軍最近忙著畢業入職的事項,好像有很長時間沒有更新了呢,今天為止上班已經一周了,有了空閑時間。

接下來的任務就是兩個話題:1. 自定義一個拍照程序,並可以在預覽界面上繪圖。2. IOS靜態庫的製作。

本節我們就來講一下第一個話題。

--------------------------- 優雅端莊可愛美麗的分割線 ------------------------

1. 自定義拍照應用

接下來我們事先一個簡單的拍照應用,功能是:預覽畫面,點擊拍照並存儲。

layout: test_preview_camera.xml

<?xml version="1.0" encoding="utf-8"?>n<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"n android:layout_width_="match_parent" android:layout_height="match_parent">nn <SurfaceViewn android:layout_width_="match_parent"n android:layout_height="match_parent"n android:id="@+id/surface_view"/>nn <Buttonn android:layout_width_="wrap_content"n android:layout_height="wrap_content"n android:id="@+id/snap_photo"n android:text="SNAP"n android:layout_alignParentBottom="true"/>nn <Buttonn android:layout_width_="wrap_content"n android:layout_height="wrap_content"n android:id="@+id/cancel_photo"n android:text="CANCEL"n android:layout_alignParentBottom="true"n android:layout_alignParentRight="true"/>nn</RelativeLayout>n

界面效果如下:

對應的java類代碼如下:

TestPreviewCameraActivity.java

package demo.edgedetection;nnimport android.app.Activity;nimport android.content.Context;nimport android.content.Intent;nimport android.hardware.Camera;nimport android.os.Bundle;nimport android.util.DisplayMetrics;nimport android.util.Log;nimport android.view.SurfaceHolder;nimport android.view.SurfaceView;nimport android.view.View;nimport android.widget.Button;nimport android.widget.ImageButton;nimport android.widget.Toast;nnimport java.io.BufferedOutputStream;nimport java.io.File;nimport java.io.FileOutputStream;nimport java.io.IOException;nimport java.util.List;nn/**n * Created by wcjzj on 2016/7/10.n */npublic class TestPreciewCameraActivity extends Activity implements SurfaceHolder.Callback, Camera.ShutterCallback, Camera.PictureCallback{nnpublic static final String FILE_PATH = "filePath";nprivate Camera mCamera;nprivate SurfaceView mPreview;nprivate Button ibSnapPhoto, ibCancelPhoto;nprivate String pictureFilePath;nn/**n * 外部調用介面n * @param contextn * @param filePath 拍攝照片的存儲地址n */n public static void callMe(Context context, String filePath){n Intent intent = new Intent(context, TestPreciewCameraActivity.class);n intent.putExtra(FILE_PATH, filePath);n context.startActivity(intent);n }nn@Overriden protected void onCreate(Bundle savedInstanceState) {nsuper.onCreate(savedInstanceState);n setContentView(R.layout.test_preview_camera);nn//mPrevie為攝像機預覽圖層n mPreview = (SurfaceView) findViewById(R.id.surface_view);nmPreview.getHolder().addCallback(this);nmPreview.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);nnibSnapPhoto = (Button) findViewById(R.id.snap_photo);nibCancelPhoto = (Button) findViewById(R.id.cancel_photo);nibSnapPhoto.setOnClickListener(new View.OnClickListener() {n@Overriden public void onClick(View v) {n onSnapPhoto();n }n });nibCancelPhoto.setOnClickListener(new View.OnClickListener() {n@Overriden public void onClick(View v) {n onCancelPhoto();n }n });nnpictureFilePath = getIntent().getStringExtra(FILE_PATH);nn//開啟Cameran mCamera = Camera.open();n }nn@Overriden protected void onPause() {nsuper.onPause();nmCamera.stopPreview();n }nn@Overriden protected void onDestroy() {nsuper.onDestroy();nmCamera.release();n }nn/**n * 點擊按鈕拍照n */n private void onSnapPhoto(){n//拍照n mCamera.takePicture(this, null, null, this);n }nn/**n * 點擊按鈕取消n */n private void onCancelPhoto(){nmCamera.stopPreview();n finish();n }nn@Overriden public void onShutter() {n Toast.makeText(TestPreciewCameraActivity.this, "click!", Toast.LENGTH_SHORT).show();n }nn@Overriden public void onPictureTaken(byte[] data, Camera camera) {ntry {nsavePictureToFile(data, pictureFilePath);n } catch (IOException e) {n Log.d("wcj", "originalImage保存失敗!");n e.printStackTrace();n }nn//重新啟動預覽n mCamera.startPreview();n }nn//以下為Surface三種回調方法n @Overriden public void surfaceCreated(SurfaceHolder holder) {n//當surfaceView建立時,在上面綁定預覽顯示界面n try{nmCamera.setPreviewDisplay(mPreview.getHolder());n }catch (Exception e){n e.printStackTrace();n }n }nn@Overriden public void surfaceDestroyed(SurfaceHolder holder) {nn }nn@Overriden public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {n Camera.Parameters params = mCamera.getParameters();n List<Camera.Size> sizes = params.getSupportedPreviewSizes();n Camera.Size selectedSize =getBestSupportPreviewSize(sizes, getScreenSize());nn//設定攝像機預覽界面尺寸n params.setPreviewSize(selectedSize.width, selectedSize.height);nmCamera.setParameters(params);nnmCamera.setDisplayOrientation(90);nmCamera.startPreview();n }nn/**n * 保存照片到存儲器n *n * @param datan * @param _filen * @throws IOExceptionn */n public static void savePictureToFile(byte[] data, String _file) throws IOException {n BufferedOutputStream os = null;ntry {n File file = new File(_file);n// String _filePath_file.replace(File.separatorChar +n // file.getName(), "");n int end = _file.lastIndexOf(File.separator);n String _filePath = _file.substring(0, end);n File filePath = new File(_filePath);nif (!filePath.exists()) {n filePath.mkdirs();n }n file.createNewFile();n os = new BufferedOutputStream(new FileOutputStream(file));n os.write(data);n } finally {nif (os != null) {ntry {n os.close();n } catch (IOException e) {n e.printStackTrace();n }n }n }n }nn/**n * 尋找最大的預覽圖片尺寸(與屏幕解析度適配)n *n * @param previewSizes 所有支持的預覽圖片大小n * @returnn */n public static Camera.Size getBestSupportPreviewSize(List<Camera.Size> previewSizes, Camera.Size screenSize) {ndouble screenRatio = screenSize.width * 1.0 / screenSize.height;n Camera.Size maxSize = previewSizes.get(0);nfor (Camera.Size size : previewSizes) {ndouble sizeRatio = size.width * 1.0 / size.height;nif (size.width < 2000 && sizeRatio > screenRatio - 0.1 && sizeRatio < screenRatio + 0.1)n maxSize = (size.width > maxSize.width) ? size : maxSize;n }nreturn maxSize;n }nnprivate Camera.Size getScreenSize() {n DisplayMetrics metric = new DisplayMetrics();n getWindowManager().getDefaultDisplay().getMetrics(metric);nint width = metric.widthPixels; // 寬度(PX)n int height = metric.heightPixels; // 高度(PX)nn return mCamera.new Size(height, width);n }n}n

SurfaceView這個視圖類採用雙緩衝機制,可以再其他線程中更新UI,是專門用來高性能繪圖的圖層,這裡是在SurfaceView上面顯示Camera的預覽畫面,點擊拍照把拍照數據保存到存儲器中。注意,預覽畫面的尺寸不能太大,否則容易引起OOM問題。

2. 獲取預覽畫面圖像數據

我們進行機器視覺的應用時,需要實時獲取到攝像頭的預覽數據,那個接下來我通過對上述類的改造完成這個需求。

a. TestPreviewCameraActivity類新增Camera.PreviewCallback的implements

b. 在其中添加如下代碼

@Overridenpublic void onPreviewFrame(byte[] data, Camera camera) {n//把data轉換為bitmapn Camera.Size size = mCamera.getParameters().getPreviewSize();ntry {n YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width,n size.height, null);nif (image != null) {n ByteArrayOutputStream stream = new ByteArrayOutputStream();n image.compressToJpeg(new Rect(0, 0, size.width, size.height),n100, stream);n Bitmap inputImage = rotateBitmap(BitmapFactory.decodeByteArray(n stream.toByteArray(), 0, stream.size()), 90);n stream.close();nn//=======================n //inputBitmap為獲取到的Bitmap,這裡對其進行後續處理n //=======================n }n } catch (Exception e) {n e.printStackTrace();n }n}nn/**n * @param bmp 要旋轉的圖片n * @param degree 圖片旋轉的角度,負值為逆時針旋轉,正值為順時針旋轉n * @return 旋轉好的圖片n */npublic static Bitmap rotateBitmap(Bitmap bmp, float degree) {n Matrix matrix = new Matrix();n matrix.postRotate(degree);n//此處bitmap默認為RGBA_8888n return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);n}n

3. 實時繪出OpenCV4Android應用得出的數據

如果我們想要把利用opencv檢測出的結果在攝像頭預覽界面顯示出來,需要繼承SurfaceView自定義一個類

這裡定義的DrawLineSurfaceView.java

package demo.edgedetection;nnimport android.content.Context;nimport android.graphics.Bitmap;nimport android.graphics.Canvas;nimport android.graphics.Color;nimport android.graphics.Paint;nimport android.graphics.PixelFormat;nimport android.graphics.PorterDuff;nimport android.graphics.PorterDuffXfermode;nimport android.util.AttributeSet;nimport android.util.Log;nimport android.view.SurfaceHolder;nimport android.view.SurfaceView;nn/**n * Created by wangchuanjun on 2016/7/10n */npublic class DrawLineSurfaceView extends SurfaceView implements SurfaceHolder.Callback {nnprotected SurfaceHolder sh;nprivate int mWidth;nprivate int mHeight;nprivate Bitmap drawBitmap;nprivate Canvas mCanvas;nprivate int[] cornerPoints;nnpublic DrawLineSurfaceView(Context context, AttributeSet attrs) {nsuper(context, attrs);nsh = getHolder();nsh.addCallback(this);nsh.setFormat(PixelFormat.TRANSPARENT);n setZOrderOnTop(true);n }nnpublic void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) {nmWidth = w;nmHeight = h;nndrawBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ALPHA_8);nmCanvas = new Canvas(drawBitmap);n }nnpublic void surfaceCreated(SurfaceHolder arg0) {nn }nnpublic void surfaceDestroyed(SurfaceHolder arg0) {nn }nnn/**n * 在預覽攝像頭上劃線n *n * @param cornerPoints 得到的4個角點n */n public void drawLine(int[] cornerPoints) {nn//繪畫n Canvas canvas = sh.lockCanvas();n canvas.drawColor(Color.TRANSPARENT);nn Paint paint = new Paint();nn//清屏n paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));n canvas.drawPaint(paint);n paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));nnif (cornerPoints == null) {n Log.d("wcj", "返回值為空");nsh.unlockCanvasAndPost(canvas);nreturn;n }nnif (cornerPoints.length == 0) {n Log.d("wcj", "返回值為空");nsh.unlockCanvasAndPost(canvas);nreturn;n }nn paint.setAntiAlias(true);n paint.setColor(Color.rgb(56, 210, 212));n paint.setStyle(Paint.Style.STROKE);n paint.setStrokeWidth(7);nn //==============n //繪圖操作n //=============== nnsh.unlockCanvasAndPost(canvas);n }nn}n

然後在test_camera_preview.xml中加入以下節點

<demo.edgedetection.DrawLineSurfaceViewn android:layout_width_="match_parent"n android:layout_height="match_parent"n android:id="@+id/draw_line_surface_view"/>n

然後在TestPreviewCameraActivity.java中調用drawline函數就可以實現動態畫圖啦

然後還可以添加自動對焦啊,手動點擊對焦啊等一些其他操作,具體就不細講啦。

4. 注意事項

實時動態在預覽界面上畫圖時記得要採用非同步在其他線程處理,以免UI線程任務太重堵塞主線程,另外處理圖片時一定要記得這是個非常重的任務,很容易引起OOM問題。

以上就是本節的任務,下一節阿軍會講一下ios的opencv靜態庫如何編譯。

[6]OpenCV4IOS靜態庫製作


推薦閱讀:

如何看待國內機器視覺行業的發展?希望以公司為例盤點!
機器視覺怎樣開始學?halcon與c++還是c#搭配好?
MXNet 的代碼要怎麼讀?
機器視覺簡介

TAG:OpenCV | Android | 机器视觉 |