diff --git a/java/android/app/src/main/AndroidManifest.xml b/java/android/app/src/main/AndroidManifest.xml index 754b0b6c3..83dfc7542 100644 --- a/java/android/app/src/main/AndroidManifest.xml +++ b/java/android/app/src/main/AndroidManifest.xml @@ -15,14 +15,14 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/MainActivity.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/MainActivity.java index bbe184b45..bdafaeb72 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/MainActivity.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/MainActivity.java @@ -9,30 +9,40 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; +import android.os.SystemClock; import android.preference.PreferenceManager; +import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.SeekBar; import android.widget.TextView; -import android.widget.Toast; import com.baidu.paddle.fastdeploy.RuntimeOption; import com.baidu.paddle.fastdeploy.app.examples.R; import com.baidu.paddle.fastdeploy.app.ui.CameraSurfaceView; -import com.baidu.paddle.fastdeploy.app.ui.Utils; +import com.baidu.paddle.fastdeploy.app.ui.view.ResultListView; +import com.baidu.paddle.fastdeploy.app.ui.view.Utils; +import com.baidu.paddle.fastdeploy.app.ui.view.adapter.DetectResultAdapter; +import com.baidu.paddle.fastdeploy.app.ui.view.model.BaseResultModel; import com.baidu.paddle.fastdeploy.vision.DetectionResult; import com.baidu.paddle.fastdeploy.vision.detection.PicoDet; import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; public class MainActivity extends Activity implements View.OnClickListener, CameraSurfaceView.OnTextureChangedListener { private static final String TAG = MainActivity.class.getSimpleName(); @@ -45,6 +55,25 @@ public class MainActivity extends Activity implements View.OnClickListener, Came ImageView realtimeToggleButton; boolean isRealtimeStatusRunning = false; ImageView backInPreview; + private ImageView albumSelectButton; + private View mCameraPageView; + private ViewGroup mResultPageView; + private ImageView resultImage; + private ImageView backInResult; + private SeekBar confidenceSeekbar; + private TextView seekbarText; + private float resultNum = 1.0f; + private ResultListView detectResultView; + private Bitmap shutterBitmap; + private Bitmap originShutterBitmap; + private Bitmap picBitmap; + private Bitmap originPicBitmap; + public static final int BTN_SHUTTER = 0; + public static final int ALBUM_SELECT = 1; + private static int TYPE = BTN_SHUTTER; + + private static final int REQUEST_PERMISSION_CODE_STORAGE = 101; + private static final int INTENT_CODE_PICK_IMAGE = 100; String savedImagePath = "result.jpg"; int lastFrameIndex = 0; @@ -83,12 +112,13 @@ public class MainActivity extends Activity implements View.OnClickListener, Came svPreview.switchCamera(); break; case R.id.btn_shutter: - @SuppressLint("SimpleDateFormat") - SimpleDateFormat date = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); - synchronized (this) { - savedImagePath = Utils.getDCIMDirectory() + File.separator + date.format(new Date()).toString() + ".png"; - } - Toast.makeText(MainActivity.this, "Save snapshot to " + savedImagePath, Toast.LENGTH_SHORT).show(); + TYPE = BTN_SHUTTER; + svPreview.onPause(); + mCameraPageView.setVisibility(View.GONE); + mResultPageView.setVisibility(View.VISIBLE); + seekbarText.setText(resultNum + ""); + confidenceSeekbar.setProgress((int) (resultNum * 100)); + resultImage.setImageBitmap(shutterBitmap); break; case R.id.btn_settings: startActivity(new Intent(MainActivity.this, SettingsActivity.class)); @@ -99,9 +129,64 @@ public class MainActivity extends Activity implements View.OnClickListener, Came case R.id.back_in_preview: finish(); break; + case R.id.albumSelect: + TYPE = ALBUM_SELECT; + // 判断是否已经赋予权限 + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + // 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。 + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CODE_STORAGE); + } else { + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setType("image/*"); + startActivityForResult(intent, INTENT_CODE_PICK_IMAGE); + } + break; + case R.id.back_in_result: + mResultPageView.setVisibility(View.GONE); + mCameraPageView.setVisibility(View.VISIBLE); + svPreview.onResume(); + break; + } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == INTENT_CODE_PICK_IMAGE) { + if (resultCode == Activity.RESULT_OK) { + mCameraPageView.setVisibility(View.GONE); + mResultPageView.setVisibility(View.VISIBLE); + seekbarText.setText(resultNum + ""); + confidenceSeekbar.setProgress((int) (resultNum * 100)); + Uri uri = data.getData(); + String path = getRealPathFromURI(uri); + picBitmap = decodeBitmap(path, 720, 1280); + originPicBitmap = picBitmap.copy(Bitmap.Config.ARGB_8888, true); + resultImage.setImageBitmap(picBitmap); + } + } + } + + private String getRealPathFromURI(Uri contentURI) { + String result; + Cursor cursor = null; + try { + cursor = getContentResolver().query(contentURI, null, null, null, null); + } catch (Throwable e) { + e.printStackTrace(); + } + if (cursor == null) { + result = contentURI.getPath(); + } else { + cursor.moveToFirst(); + int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); + result = cursor.getString(idx); + cursor.close(); + } + return result; + } + private void toggleRealtimeStyle() { if (isRealtimeStatusRunning) { isRealtimeStatusRunning = false; @@ -125,8 +210,10 @@ public class MainActivity extends Activity implements View.OnClickListener, Came public boolean onTextureChanged(Bitmap ARGB8888ImageBitmap) { String savedImagePath = ""; synchronized (this) { - savedImagePath = MainActivity.this.savedImagePath; + savedImagePath = Utils.getDCIMDirectory() + File.separator + "result.png"; } + shutterBitmap = ARGB8888ImageBitmap.copy(Bitmap.Config.ARGB_8888,true); + originShutterBitmap = ARGB8888ImageBitmap.copy(Bitmap.Config.ARGB_8888,true); boolean modified = false; DetectionResult result = predictor.predict( ARGB8888ImageBitmap, savedImagePath, SettingsActivity.scoreThreshold); @@ -151,6 +238,38 @@ public class MainActivity extends Activity implements View.OnClickListener, Came return modified; } + /** + * @param path 路径 + * @param displayWidth 需要显示的宽度 + * @param displayHeight 需要显示的高度 + * @return Bitmap + */ + public static Bitmap decodeBitmap(String path, int displayWidth, int displayHeight) { + BitmapFactory.Options op = new BitmapFactory.Options(); + op.inJustDecodeBounds = true; + // op.inJustDecodeBounds = true;表示我们只读取Bitmap的宽高等信息,不读取像素。 + Bitmap bmp = BitmapFactory.decodeFile(path, op); // 获取尺寸信息 + // op.outWidth表示的是图像真实的宽度 + // op.inSamplySize 表示的是缩小的比例 + // op.inSamplySize = 4,表示缩小1/4的宽和高,1/16的像素,android认为设置为2是最快的。 + // 获取比例大小 + int wRatio = (int) Math.ceil(op.outWidth / (float) displayWidth); + int hRatio = (int) Math.ceil(op.outHeight / (float) displayHeight); + // 如果超出指定大小,则缩小相应的比例 + if (wRatio > 1 && hRatio > 1) { + if (wRatio > hRatio) { + // 如果太宽,我们就缩小宽度到需要的大小,注意,高度就会变得更加的小。 + op.inSampleSize = wRatio; + } else { + op.inSampleSize = hRatio; + } + } + op.inJustDecodeBounds = false; + bmp = BitmapFactory.decodeFile(path, op); + // 从原Bitmap创建一个给定宽高的Bitmap + return Bitmap.createScaledBitmap(bmp, displayWidth, displayHeight, true); + } + @Override protected void onResume() { super.onResume(); @@ -191,6 +310,63 @@ public class MainActivity extends Activity implements View.OnClickListener, Came realtimeToggleButton.setOnClickListener(this); backInPreview = findViewById(R.id.back_in_preview); backInPreview.setOnClickListener(this); + albumSelectButton = findViewById(R.id.albumSelect); + albumSelectButton.setOnClickListener(this); + mCameraPageView = findViewById(R.id.camera_page); + mResultPageView = findViewById(R.id.result_page); + resultImage = findViewById(R.id.result_image); + backInResult = findViewById(R.id.back_in_result); + backInResult.setOnClickListener(this); + confidenceSeekbar = findViewById(R.id.confidence_seekbar); + seekbarText = findViewById(R.id.seekbar_text); + detectResultView = findViewById(R.id.result_list_view); + + List results = new ArrayList<>(); + results.add(new BaseResultModel(1, "cup", 0.4f)); + results.add(new BaseResultModel(2, "pen", 0.6f)); + results.add(new BaseResultModel(3, "tang", 1.0f)); + final DetectResultAdapter adapter = new DetectResultAdapter(this, R.layout.result_detect_item, results); + detectResultView.setAdapter(adapter); + detectResultView.invalidate(); + + confidenceSeekbar.setMax(100); + confidenceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + float resultConfidence = seekBar.getProgress() / 100f; + BigDecimal bd = new BigDecimal(resultConfidence); + resultNum = bd.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue(); + seekbarText.setText(resultNum + ""); + confidenceSeekbar.setProgress((int) (resultNum * 100)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (TYPE == ALBUM_SELECT) { + SystemClock.sleep(500); + predictor.predict(picBitmap, savedImagePath, resultNum); + resultImage.setImageBitmap(picBitmap); + picBitmap = originPicBitmap.copy(Bitmap.Config.ARGB_8888, true); + resultNum = 1.0f; + } else { + SystemClock.sleep(500); + predictor.predict(shutterBitmap, savedImagePath, resultNum); + resultImage.setImageBitmap(shutterBitmap); + shutterBitmap = originShutterBitmap.copy(Bitmap.Config.ARGB_8888, true); + resultNum = 1.0f; + } + } + }); + } + }); } @SuppressLint("ApplySharedPref") diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/SettingsActivity.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/SettingsActivity.java index 17e168f07..edae95ba8 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/SettingsActivity.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/detection/SettingsActivity.java @@ -11,7 +11,7 @@ import android.support.v7.app.ActionBar; import com.baidu.paddle.fastdeploy.app.examples.R; import com.baidu.paddle.fastdeploy.app.ui.AppCompatPreferenceActivity; -import com.baidu.paddle.fastdeploy.app.ui.Utils; +import com.baidu.paddle.fastdeploy.app.ui.view.Utils; import java.util.ArrayList; import java.util.List; @@ -46,8 +46,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity implements @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // addPreferencesFromResource(R.xml.settings); - addPreferencesFromResource(R.xml.detection_settings); + addPreferencesFromResource(R.xml.settings); ActionBar supportActionBar = getSupportActionBar(); if (supportActionBar != null) { supportActionBar.setDisplayHomeAsUpEnabled(true); diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/MainActivity.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/MainActivity.java index 0ea0486e0..df53d42eb 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/MainActivity.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/MainActivity.java @@ -26,7 +26,7 @@ import android.widget.Toast; import com.baidu.paddle.fastdeploy.RuntimeOption; import com.baidu.paddle.fastdeploy.app.examples.R; import com.baidu.paddle.fastdeploy.app.ui.CameraSurfaceView; -import com.baidu.paddle.fastdeploy.app.ui.Utils; +import com.baidu.paddle.fastdeploy.app.ui.view.Utils; import com.baidu.paddle.fastdeploy.vision.OCRResult; import com.baidu.paddle.fastdeploy.pipeline.PPOCRv2; import com.baidu.paddle.fastdeploy.vision.ocr.Classifier; diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/SettingsActivity.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/SettingsActivity.java index ea2c4ff72..27b0c9e43 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/SettingsActivity.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/examples/ocr/SettingsActivity.java @@ -11,7 +11,7 @@ import android.support.v7.app.ActionBar; import com.baidu.paddle.fastdeploy.app.examples.R; import com.baidu.paddle.fastdeploy.app.ui.AppCompatPreferenceActivity; -import com.baidu.paddle.fastdeploy.app.ui.Utils; +import com.baidu.paddle.fastdeploy.app.ui.view.Utils; import java.util.ArrayList; import java.util.List; diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/CameraSurfaceView.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/CameraSurfaceView.java index 6db97d68a..ba26dbebe 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/CameraSurfaceView.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/CameraSurfaceView.java @@ -15,6 +15,8 @@ import android.opengl.Matrix; import android.util.AttributeSet; import android.util.Log; +import com.baidu.paddle.fastdeploy.app.ui.view.Utils; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraListener.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraListener.java new file mode 100644 index 000000000..377de1b2e --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraListener.java @@ -0,0 +1,19 @@ +package com.baidu.paddle.fastdeploy.app.ui.camera; + +import android.graphics.Bitmap; + +/** + * Created by ruanshimin on 2017/11/30. + */ + +public class CameraListener { + public interface CommonListener { + void onSurfaceReady(); + + void onSwitchCamera(); + } + + public interface TakePictureListener { + void onTakenPicture(Bitmap bitmap); + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraProxy1.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraProxy1.java new file mode 100644 index 000000000..0836ac640 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/CameraProxy1.java @@ -0,0 +1,296 @@ +package com.baidu.paddle.fastdeploy.app.ui.camera; + +import static android.hardware.Camera.getCameraInfo; +import static android.hardware.Camera.getNumberOfCameras; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.graphics.YuvImage; +import android.hardware.Camera; +import android.util.Log; + +import com.baidu.paddle.fastdeploy.app.ui.util.ImageUtil; +import com.baidu.paddle.fastdeploy.app.ui.util.UiLog; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by ruanshimin on 2018/4/16. + */ + +public class CameraProxy1 implements ICameraProxy { + + private static final int STATUS_INIT = 0; + + private static final int STATUS_OPENED = 1; + + private static final int STATUS_PREVIEWING = 2; + + private static final int TEXTURE_STATUS_INITED = 0; + + private static final int TEXTURE_STATUS_READY = 1; + + private SurfaceTexture mSurfaceTexture; + + private boolean isBack = true; + + private Camera mCamera; + + private int status; + private int textureStatus; + + private boolean hasDestoryUnexcepted = false; + + private int layoutWidth; + private int layoutHeight; + + private int[] previewSize = new int[2]; + + private CameraListener.CommonListener mCameraListener; + + private int minPreviewCandidateWidth = 0; + + private int previewRotation = 90; + private int displayRotation = 0; + + private int previewCaptureRotation = 0; + + private Camera.Parameters cameraParameters; + + public CameraProxy1(int width, int height) { + layoutWidth = width; + layoutHeight = height; + status = STATUS_INIT; + textureStatus = TEXTURE_STATUS_INITED; + } + + public int[] getPreviewSize() { + return previewSize; + } + + private Camera.Size getPreviewSize(List list) { + ArrayList validSizeList = new ArrayList(); + float ratio = (float) layoutHeight / layoutWidth; + for (Camera.Size size : list) { + if ((float) size.width / size.height >= ratio && size.width > minPreviewCandidateWidth) { + validSizeList.add(size); + } + } + + // 没有符合条件的,直接返回第一个吧 + if (validSizeList.size() == 0) { + UiLog.info("no valid preview size"); + return list.get(0); + } + + Camera.Size size = (Camera.Size) Collections.min(validSizeList, new Comparator() { + @Override + public int compare(Camera.Size s1, Camera.Size s2) { + return s1.width - s2.width; + } + }); + + return (Camera.Size) validSizeList.get(1); + // return size; + } + + + public Camera open(boolean isOpenBack) { + int numberOfCameras = getNumberOfCameras(); + UiLog.info("cameraNumber is " + numberOfCameras); + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); + for (int i = 0; i < numberOfCameras; i++) { + getCameraInfo(i, cameraInfo); + if (isOpenBack) { + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { + caculatePreviewRotation(cameraInfo); + return Camera.open(i); + } + } else { + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + caculatePreviewRotation(cameraInfo); + return Camera.open(i); + } + } + + } + // 兼容只有前置摄像头的开发板 + return Camera.open(0); + } + + public void setDisplayRotation(int degree) { + displayRotation = degree; + } + + private void caculatePreviewRotation(Camera.CameraInfo info) { + int degree; + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + degree = (info.orientation + displayRotation) % 360; + degree = (360 - degree) % 360; // compensate the mirror + previewCaptureRotation = (360 - degree) % 360; + } else { // back-facing + degree = (info.orientation - displayRotation + 360) % 360; + previewCaptureRotation = (360 + degree) % 360; + } + + previewRotation = degree; + } + + @Override + public void startPreview() { + // MIUI上会在activity stop时destory关闭相机,触发textureview的destory方法 + if (hasDestoryUnexcepted) { + openCamera(); + } + // 如果已经TextureAvailable,设置texture并且预览,(必须先调用了openCamera) + if (textureStatus == TEXTURE_STATUS_READY) { + try { + mCamera.setPreviewTexture(mSurfaceTexture); + } catch (IOException e) { + e.printStackTrace(); + } + + mCamera.startPreview(); + mCamera.setPreviewCallback(mPreviewCallback); + status = STATUS_PREVIEWING; + return; + } + } + + public void resumePreview() { + mCamera.startPreview(); + } + + private byte[] currentFrameData; + + private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + // 在某些机型和某项项目中,某些帧的data的数据不符合nv21的格式,需要过滤,否则后续处理会导致crash + if (data.length != cameraParameters.getPreviewSize().width + * cameraParameters.getPreviewSize().height * 1.5) { + return; + } + currentFrameData = data; + } + }; + + public void openCamera() { + mCamera = open(isBack); + cameraParameters = mCamera.getParameters(); + Camera.Size previewSize = cameraParameters.getPreviewSize(); + List supportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes(); + Camera.Size size = getPreviewSize(supportedPreviewSizes); + if (previewRotation == 90 || previewRotation == 270) { + this.previewSize[0] = size.height; + this.previewSize[1] = size.width; + } else { + this.previewSize[0] = size.width; + this.previewSize[1] = size.height; + } + previewSize.height = size.height; + if (isBack && cameraParameters.getSupportedFocusModes().contains( + Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + cameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + } + cameraParameters.setPreviewSize(size.width, size.height); + mCamera.setDisplayOrientation(previewRotation); + mCamera.setParameters(cameraParameters); + status = STATUS_OPENED; + if (mCameraListener != null) { + mCameraListener.onSwitchCamera(); + } + } + + public void closeCamera() { + stopPreview(); + mCamera.setPreviewCallback(null); + mCamera.release(); + mCamera = null; + status = STATUS_INIT; + } + + @Override + public void stopPreview() { + + mCamera.stopPreview(); + mCamera.setPreviewCallback(null); + status = STATUS_OPENED; + Log.e("literrr", "stoped"); + } + + @Override + public void switchSide() { + isBack = !isBack; + stopPreview(); + closeCamera(); + openCamera(); + startPreview(); + } + + private Bitmap convertPreviewDataToBitmap(byte[] data) { + Camera.Size size = cameraParameters.getPreviewSize(); + YuvImage img = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); + ByteArrayOutputStream os = null; + os = new ByteArrayOutputStream(data.length); + img.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, os); + byte[] jpeg = os.toByteArray(); + Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length); + Bitmap bitmapRotate; + if (isBack) { + bitmapRotate = ImageUtil.createRotateBitmap(bitmap, previewCaptureRotation); + } else { + bitmapRotate = ImageUtil.createMirrorRotateBitmap(bitmap, previewCaptureRotation); + } + try { + os.close(); + } catch (IOException e) { + UiLog.info("convertPreviewDataToBitmap close bitmap"); + } + return bitmapRotate; + } + + @Override + public void takePicture(CameraListener.TakePictureListener listener) { + if (currentFrameData != null) { + Bitmap bitmap = convertPreviewDataToBitmap(currentFrameData); + listener.onTakenPicture(bitmap); + UiLog.info("convert bitmap success"); + } + } + + @Override + public void setEventListener(CameraListener.CommonListener listener) { + mCameraListener = listener; + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + mSurfaceTexture = surface; + textureStatus = TEXTURE_STATUS_READY; + mCameraListener.onSurfaceReady(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + hasDestoryUnexcepted = true; + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/ICameraProxy.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/ICameraProxy.java new file mode 100644 index 000000000..9e656d4ea --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/camera/ICameraProxy.java @@ -0,0 +1,27 @@ +package com.baidu.paddle.fastdeploy.app.ui.camera; + +import android.view.TextureView; + + +/** + * Created by ruanshimin on 2017/3/29. + */ +public interface ICameraProxy extends TextureView.SurfaceTextureListener { + void openCamera(); + + void setDisplayRotation(int degree); + + void startPreview(); + + void stopPreview(); + + void closeCamera(); + + void switchSide(); + + int[] getPreviewSize(); + + void takePicture(CameraListener.TakePictureListener listener); + + void setEventListener(CameraListener.CommonListener commonListener); +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/ActionBarLayout.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/ActionBarLayout.java similarity index 94% rename from java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/ActionBarLayout.java rename to java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/ActionBarLayout.java index 6616a290b..099219fa9 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/ActionBarLayout.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/ActionBarLayout.java @@ -1,4 +1,4 @@ -package com.baidu.paddle.fastdeploy.app.ui; +package com.baidu.paddle.fastdeploy.app.ui.layout; import android.content.Context; import android.graphics.Color; diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/OperationFrameLayout.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/OperationFrameLayout.java new file mode 100644 index 000000000..1d11b5a3c --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/layout/OperationFrameLayout.java @@ -0,0 +1,37 @@ +package com.baidu.paddle.fastdeploy.app.ui.layout; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.RelativeLayout; + +/** + * Created by ruanshimin on 2018/5/3. + */ + +public class OperationFrameLayout extends RelativeLayout { + private int layoutHeight = 360; + + public OperationFrameLayout(@NonNull Context context) { + super(context); + } + + public OperationFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public OperationFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, layoutHeight); + setBackgroundColor(Color.BLACK); + } + +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ImageUtil.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ImageUtil.java new file mode 100644 index 000000000..85a36e9d8 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ImageUtil.java @@ -0,0 +1,155 @@ +package com.baidu.paddle.fastdeploy.app.ui.util; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.util.Log; + +/** + * Created by ruanshimin on 2018/5/8. + */ + +public class ImageUtil { + private static final String TAG = "CameraExif"; + + public static Bitmap createRotateBitmap(Bitmap bitmap, int rotation) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + return Bitmap.createBitmap( + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); + } + + public static Bitmap createMirrorRotateBitmap(Bitmap bitmap, int rotation) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + matrix.postScale(-1, 1); + return Bitmap.createBitmap( + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); + } + + public static int exifToDegrees(int exifOrientation) { + if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { + return 90; + } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { + return 180; + } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { + return 270; + } + return 0; + } + + // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. + public static int getOrientation(byte[] jpeg) { + if (jpeg == null) { + return 0; + } + + int offset = 0; + int length = 0; + + // ISO/IEC 10918-1:1993(E) + while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) { + int marker = jpeg[offset] & 0xFF; + + // Check if the marker is a padding. + if (marker == 0xFF) { + continue; + } + offset++; + + // Check if the marker is SOI or TEM. + if (marker == 0xD8 || marker == 0x01) { + continue; + } + // Check if the marker is EOI or SOS. + if (marker == 0xD9 || marker == 0xDA) { + break; + } + + // Get the length and check if it is reasonable. + length = pack(jpeg, offset, 2, false); + if (length < 2 || offset + length > jpeg.length) { + Log.e(TAG, "Invalid length"); + return 0; + } + + // Break if the marker is EXIF in APP1. + if (marker == 0xE1 && length >= 8 + && pack(jpeg, offset + 2, 4, false) == 0x45786966 + && pack(jpeg, offset + 6, 2, false) == 0) { + offset += 8; + length -= 8; + break; + } + + // Skip other markers. + offset += length; + length = 0; + } + + // JEITA CP-3451 Exif Version 2.2 + if (length > 8) { + // Identify the byte order. + int tag = pack(jpeg, offset, 4, false); + if (tag != 0x49492A00 && tag != 0x4D4D002A) { + Log.e(TAG, "Invalid byte order"); + return 0; + } + boolean littleEndian = (tag == 0x49492A00); + + // Get the offset and check if it is reasonable. + int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; + if (count < 10 || count > length) { + Log.e(TAG, "Invalid offset"); + return 0; + } + offset += count; + length -= count; + + // Get the count and go through all the elements. + count = pack(jpeg, offset - 2, 2, littleEndian); + while (count-- > 0 && length >= 12) { + // Get the tag and check if it is orientation. + tag = pack(jpeg, offset, 2, littleEndian); + if (tag == 0x0112) { + // We do not really care about type and count, do we? + int orientation = pack(jpeg, offset + 8, 2, littleEndian); + switch (orientation) { + case 1: + return 0; + case 3: + return 180; + case 6: + return 90; + case 8: + return 270; + default: + return 0; + } + } + offset += 12; + length -= 12; + } + } + + Log.i(TAG, "Orientation not found"); + return 0; + } + + private static int pack(byte[] bytes, int offset, int length, + boolean littleEndian) { + int step = 1; + if (littleEndian) { + offset += length - 1; + step = -1; + } + + int value = 0; + while (length-- > 0) { + value = (value << 8) | (bytes[offset] & 0xFF); + offset += step; + } + return value; + } + +} \ No newline at end of file diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/StringUtil.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/StringUtil.java new file mode 100644 index 000000000..c36e37090 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/StringUtil.java @@ -0,0 +1,14 @@ +package com.baidu.paddle.fastdeploy.app.ui.util; + +import java.text.DecimalFormat; + +/** + * Created by ruanshimin on 2018/5/24. + */ + +public class StringUtil { + public static String formatFloatString(float number) { + DecimalFormat df = new DecimalFormat("0.00"); + return df.format(number); + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ThreadPoolManager.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ThreadPoolManager.java new file mode 100644 index 000000000..00772e74f --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/ThreadPoolManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. + */ +package com.baidu.paddle.fastdeploy.app.ui.util; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ThreadPoolManager { + + static Timer timerFocus = null; + + /* + * 对焦频率 + */ + static final long cameraScanInterval = 2000; + + /* + * 线程池大小 + */ + private static int poolCount = Runtime.getRuntime().availableProcessors(); + + private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(poolCount); + + private static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); + + /** + * 给线程池添加任务 + * + * @param runnable 任务 + */ + public static void execute(Runnable runnable) { + fixedThreadPool.execute(runnable); + } + + /** + * 单独线程任务 + * + * @param runnable 任务 + */ + public static void executeSingle(Runnable runnable) { + singleThreadPool.execute(runnable); + } + + /** + * 创建一个定时对焦的timer任务 + * + * @param runnable 对焦代码 + * @return Timer Timer对象,用来终止自动对焦 + */ + public static Timer createAutoFocusTimerTask(final Runnable runnable) { + if (timerFocus != null) { + return timerFocus; + } + timerFocus = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + runnable.run(); + } + }; + timerFocus.scheduleAtFixedRate(task, 0, cameraScanInterval); + return timerFocus; + } + + /** + * 终止自动对焦任务,实际调用了cancel方法并且清空对象 + * 但是无法终止执行中的任务,需额外处理 + */ + public static void cancelAutoFocusTimer() { + if (timerFocus != null) { + timerFocus.cancel(); + timerFocus = null; + } + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/UiLog.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/UiLog.java new file mode 100644 index 000000000..ebf6c4111 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/util/UiLog.java @@ -0,0 +1,15 @@ +package com.baidu.paddle.fastdeploy.app.ui.util; + +import android.util.Log; + +/** + * Created by ruanshimin on 2018/5/4. + */ + +public class UiLog { + private static final String TAG = "edge_log"; + + public static void info(String log) { + Log.i(TAG, log); + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewDecoratorView.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewDecoratorView.java new file mode 100644 index 000000000..eefca9214 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewDecoratorView.java @@ -0,0 +1,162 @@ +package com.baidu.paddle.fastdeploy.app.ui.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + +import com.baidu.paddle.fastdeploy.app.examples.R; + + +/** + * Created by ruanshimin on 2018/5/7. + */ + +public class PreviewDecoratorView extends View { + + static final String RUNNING_HINT_TEXT = "对准识别物体,自动识别物体"; + + static final String STOP_HINT_TEXT = "已暂定实时识别,请点击下方按钮开启"; + + private String hintText = STOP_HINT_TEXT; + + public PreviewDecoratorView(Context context) { + super(context); + } + + public PreviewDecoratorView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public PreviewDecoratorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + public void setStatus(boolean isRunning) { + if (isRunning) { + hintText = RUNNING_HINT_TEXT; + } else { + hintText = STOP_HINT_TEXT; + } + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int offset = 50; + int offsetBottom = 170; + int stokeWidth = 15; + int extendOffset = stokeWidth / 2; + int height = getHeight(); + int width = getWidth(); + int barSize = 40; + + + canvas.save(); + + Paint paintBar = new Paint(); + paintBar.setColor(Color.WHITE); + paintBar.setStrokeWidth(stokeWidth); + paintBar.setStyle(Paint.Style.STROKE); + + + Point leftTop = new Point(offset, offset); + Point rightTop = new Point(width - offset, offset); + Point leftBottom = new Point(offset, height - offsetBottom); + Point rightBottom = new Point(width - offset, height - offsetBottom); + + + Path path = new Path(); + path.moveTo(leftTop.x, leftTop.y + barSize); + path.lineTo(leftTop.x, leftTop.y); + path.lineTo(leftTop.x + barSize, leftTop.y); + + path.moveTo(rightTop.x - barSize, leftTop.y); + path.lineTo(rightTop.x, rightTop.y); + path.lineTo(rightTop.x, rightTop.y + barSize); + + path.moveTo(leftBottom.x, leftBottom.y - barSize); + path.lineTo(leftBottom.x, leftBottom.y); + path.lineTo(leftBottom.x + barSize, leftBottom.y); + + path.moveTo(rightBottom.x - barSize, rightBottom.y); + path.lineTo(rightBottom.x, rightBottom.y); + path.lineTo(rightBottom.x, rightBottom.y - barSize); + + + // 绘制半透明遮罩 + Rect rect = new Rect(0, 0, width, height); + Paint paint = new Paint(); + paint.setColor(Color.BLACK); + paint.setAlpha(50); + canvas.drawRect(rect, paint); + + // 擦除可见部分半透明遮罩 + rect = new Rect(leftTop.x - extendOffset, leftTop.y - extendOffset, + rightBottom.x + extendOffset, rightBottom.y + extendOffset); + canvas.clipRect(rect); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + // 绘制四个小角 + canvas.drawPath(path, paintBar); + canvas.restore(); + // 设置留给hint的位置 + rect = new Rect(0, rightBottom.y + extendOffset, width, height); + + drawHint(canvas, rect, hintText); + + super.onDraw(canvas); + } + + public void drawHint(Canvas canvas, Rect rect, String text) { + int textSizePx = 16; + int imageSizePx = 48; + int padInTextAndIcon = 22; + Paint textPaint = new Paint(); + textPaint.setTextSize(textSizePx); + textPaint.setColor(Color.WHITE); + textPaint.setTextAlign(Paint.Align.LEFT); + + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scan_icon); + + float textWidth = getTextWidth(this.getContext(), text, textPaint, textSizePx); + int width = padInTextAndIcon + imageSizePx + (int) textWidth; + int height = imageSizePx; + + int startX = rect.left + (rect.width() - width) / 2; + int startY = rect.top + (rect.height() - height) / 2; + + Rect srcRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + Rect destRect = new Rect(startX, startY, startX + imageSizePx, startY + imageSizePx); + + Paint mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBitPaint.setFilterBitmap(true); + mBitPaint.setDither(true); + canvas.drawBitmap(bitmap, srcRect, destRect, mBitPaint); + + startX += padInTextAndIcon + imageSizePx; + + canvas.drawText(text, startX, startY + textSizePx + 23, textPaint); + } + + public float getTextWidth(Context context, String text, Paint paint, int textSize) { + float scaledDensity = getResources().getDisplayMetrics().scaledDensity; + paint.setTextSize(scaledDensity * textSize); + return paint.measureText(text); + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewView.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewView.java new file mode 100644 index 000000000..0d01c7cf1 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/PreviewView.java @@ -0,0 +1,144 @@ +package com.baidu.paddle.fastdeploy.app.ui.view; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.Surface; +import android.view.TextureView; + +import com.baidu.paddle.fastdeploy.app.ui.camera.CameraListener; +import com.baidu.paddle.fastdeploy.app.ui.camera.CameraProxy1; +import com.baidu.paddle.fastdeploy.app.ui.camera.ICameraProxy; + +/** + * Created by ruanshimin on 2018/5/3. + */ + +public class PreviewView extends TextureView { + ICameraProxy mCameraProxy; + private int layoutWidth; + private int layoutHeight; + private int actualHeight; + private int cropHeight; + + public PreviewView(Context context) { + super(context); + } + + public PreviewView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PreviewView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + /** + * 手动调用开始预览 + */ + public void start() { + if (mCameraProxy != null) { + mCameraProxy.startPreview(); + } + } + + /** + * 释放摄像头 + */ + public void destory() { + if (mCameraProxy != null) { + mCameraProxy.closeCamera(); + } + } + + /** + * 手动停止 + */ + public void stopPreview() { + mCameraProxy.stopPreview(); + } + + /** + * 设置实际可见layout的长宽 + */ + public void setLayoutSize(int width, int height) { + layoutWidth = width; + layoutHeight = height; + } + + public void takePicture(final CameraListener.TakePictureListener listener) { + // 裁剪图片 + mCameraProxy.takePicture(new CameraListener.TakePictureListener() { + @Override + public void onTakenPicture(Bitmap bitmap) { + Bitmap cropBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), cropHeight); + listener.onTakenPicture(cropBitmap); + } + }); + } + + /** + * 切换摄像头面 + */ + public void switchSide() { + mCameraProxy.switchSide(); + } + + public int getActualHeight() { + return actualHeight; + } + + private void setDisplayDegree() { + int rotation = ((Activity) this.getContext()).getWindowManager().getDefaultDisplay() + .getRotation(); + int degrees = 0; + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + default: + degrees = 0; + } + mCameraProxy.setDisplayRotation(degrees); + } + + private void refreshCropHeight() { + int[] size = mCameraProxy.getPreviewSize(); + actualHeight = (int) (((float) size[1] / size[0]) * layoutWidth); + cropHeight = (int) (((float) layoutHeight / layoutWidth) * size[0]); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mCameraProxy == null) { + mCameraProxy = new CameraProxy1(layoutWidth, layoutHeight); + setDisplayDegree(); + setSurfaceTextureListener(mCameraProxy); + mCameraProxy.openCamera(); + refreshCropHeight(); + mCameraProxy.setEventListener(new CameraListener.CommonListener() { + @Override + public void onSurfaceReady() { + mCameraProxy.startPreview(); + } + + @Override + public void onSwitchCamera() { + refreshCropHeight(); + } + }); + } + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultListView.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultListView.java new file mode 100644 index 000000000..a028c0f9c --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultListView.java @@ -0,0 +1,47 @@ +package com.baidu.paddle.fastdeploy.app.ui.view; + +import android.content.Context; +import android.os.Handler; +import android.util.AttributeSet; +import android.widget.ListView; + +/** + * Created by ruanshimin on 2018/5/14. + */ + +public class ResultListView extends ListView { + public ResultListView(Context context) { + super(context); + } + + public ResultListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ResultListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private Handler handler; + + public void setHandler(Handler mHandler) { + handler = mHandler; + } + + public void clear() { + handler.post(new Runnable() { + @Override + public void run() { + removeAllViewsInLayout(); + invalidate(); + } + }); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, + MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultMaskView.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultMaskView.java new file mode 100644 index 000000000..13e3241ee --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/ResultMaskView.java @@ -0,0 +1,381 @@ +package com.baidu.paddle.fastdeploy.app.ui.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + +import com.baidu.paddle.fastdeploy.app.ui.util.StringUtil; +import com.baidu.paddle.fastdeploy.app.ui.view.model.BasePolygonResultModel; +import com.baidu.paddle.fastdeploy.app.ui.view.model.PoseViewResultModel; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Created by ruanshimin on 2018/5/15. + */ + +public class ResultMaskView extends View { + private static final int[][] COLOR_MAP_DEF = { + {0, 0, 0}, + {244, 35, 232}, + {70, 70, 70}, + {102, 102, 156}, + {190, 153, 153}, + {153, 153, 153}, + {250, 170, 30}, + {220, 220, 0}, + {107, 142, 35}, + {152, 251, 152}, + {70, 130, 180}, + {220, 20, 60}, + {255, 0, 0}, + {0, 0, 142}, + {0, 0, 70}, + {0, 60, 100}, + {0, 80, 100}, + {0, 0, 230}, + {119, 11, 32}, + {128, 64, 128} + }; + + private float sizeRatio; + private List mResultModelList; + private Point originPt = new Point(); + private int imgWidth; + private int imgHeight; + private Paint textPaint; + + private Handler handler; + + public void setHandler(Handler mHandler) { + handler = mHandler; + } + + public ResultMaskView(Context context) { + super(context); + } + + private Map paintFixPool = new HashMap<>(); + + public ResultMaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + + public void setPolygonListInfo(List modelList, int width, int height) { + imgWidth = width; + imgHeight = height; + mResultModelList = modelList; + handler.post(new Runnable() { + @Override + public void run() { + invalidate(); + } + }); + } + + + public void clear() { + mResultModelList = null; + handler.post(new Runnable() { + @Override + public void run() { + invalidate(); + } + }); + } + + private void preCaculate() { + float ratio = (float) getMeasuredWidth() / (float) getMeasuredHeight(); + float ratioBitmap = (float) imgWidth / (float) imgHeight; + // | |#####| |模式 + if (ratioBitmap < ratio) { + sizeRatio = (float) getMeasuredHeight() / (float) imgHeight; + int x = (int) (getMeasuredWidth() - sizeRatio * imgWidth) / 2; + originPt.set(x, 0); + } else { + // ------------ + // + // ------------ + // ############ + // ------------ + // + // ------------ + sizeRatio = (float) getMeasuredWidth() / (float) imgWidth; + int y = (int) (getMeasuredHeight() - sizeRatio * imgHeight) / 2; + originPt.set(0, y); + } + + } + + private Map paintRandomPool = new HashMap<>(); + + public ResultMaskView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + textPaint = new Paint(); + textPaint.setTextSize(30); + textPaint.setARGB(255, 255, 255, 255); + } + + private Paint getRandomMaskPaint(int index) { + if (paintRandomPool.containsKey(index)) { + + return paintRandomPool.get(index); + } + + int[] seed = new int[3]; + int offset = index % 3; + seed[offset] = 255; + + Paint paint = new Paint(); + Random rnd = new Random(); + paint.setARGB(170, + (rnd.nextInt(255) + seed[0]) / 2, + (rnd.nextInt(255) + seed[1]) / 2, + (rnd.nextInt(255) + seed[2]) / 2); + paint.setStyle(Paint.Style.FILL_AND_STROKE); + + paintRandomPool.put(index, paint); + + return paint; + } + + private Paint getFixColorPaint(int colorId) { + + // float alpha = 90; + String[] colors = {"#FF0000", "#FF0000", "#00FF00", "#0000FF", "#FF00FF"}; + int index = colorId % colors.length; + if (paintFixPool.containsKey(index)) { + return paintFixPool.get(index); + } + + int color = Color.parseColor(colors[index]); + Paint paint = new Paint(); + paint.setColor(color); + //paint.setAlpha(90); + paintFixPool.put(index, paint); + return paint; + } + + @Override + protected void onDraw(Canvas canvas) { + // 实时识别的时候第一次渲染 + if (mResultModelList == null) { + super.onDraw(canvas); + return; + } + + preCaculate(); + + int stokeWidth = 5; + + int fontSize = 38; + int labelPadding = 5; + int labelHeight = 46 + 2 * labelPadding; + Paint paint = new Paint(); + paint.setColor(Color.parseColor("#3B85F5")); + paint.setStrokeWidth(stokeWidth); + paint.setStyle(Paint.Style.STROKE); + + Paint paintFillAlpha = new Paint(); + paintFillAlpha.setStyle(Paint.Style.FILL); + paintFillAlpha.setColor(Color.parseColor("#3B85F5")); + paintFillAlpha.setAlpha(50); + + Paint paintFill = new Paint(); + paintFill.setStyle(Paint.Style.FILL); + paintFill.setColor(Color.parseColor("#3B85F5")); + + Paint paintText = new Paint(); + paintText.setColor(Color.WHITE); + paintText.setTextAlign(Paint.Align.LEFT); + paintText.setTextSize(fontSize); + DecimalFormat df = new DecimalFormat("0.00"); + + + List points; + for (int i = 0; i < mResultModelList.size(); i++) { + + BasePolygonResultModel model = mResultModelList.get(i); + Path path = new Path(); + List polygon = model.getBounds(sizeRatio, originPt); + + path.moveTo(polygon.get(0).x, polygon.get(0).y); + for (int j = 1; j < polygon.size(); j++) { + path.lineTo(polygon.get(j).x, polygon.get(j).y); + } + path.close(); + + if (model.isDrawPoints()) { + Paint paintFillPoint = new Paint(); + paintFillPoint.setStyle(Paint.Style.FILL_AND_STROKE); + + paintFillPoint.setColor(Color.YELLOW); + List polygonPoints = model.getBounds(sizeRatio, originPt); + for (Point p : polygonPoints) { + canvas.drawCircle(p.x, p.y, 10 * sizeRatio, paintFillPoint); + } + + } + + // 绘制框 + if (!model.isHasMask()) { + if (model instanceof PoseViewResultModel && model.isHasGroupColor()) { + paint = getFixColorPaint(model.getColorId()); + paint.setStrokeWidth(stokeWidth); + paint.setStyle(Paint.Style.STROKE); + paint.setAlpha(90); + } + canvas.drawPath(path, paint); + canvas.drawPath(path, paintFillAlpha); + if (model.isRect()) { + Rect rect = model.getRect(sizeRatio, originPt); + canvas.drawRect(new Rect(rect.left, rect.top, rect.right, + rect.top + labelHeight), paintFill); + } + } + if (model.isRect()) { + Rect rect = model.getRect(sizeRatio, originPt); + canvas.drawText(model.getName() + " " + StringUtil.formatFloatString(model.getConfidence()), + rect.left + labelPadding, + rect.top + fontSize + labelPadding, paintText); + } + + if (model.isTextOverlay()) { + canvas.drawText(model.getName(), + polygon.get(0).x, polygon.get(0).y, textPaint); + } + + // 绘制mask + if (model.isHasMask()) { + if (!model.isSemanticMask()) { + // 实例分割绘制 + Paint paintMask = getRandomMaskPaint(model.getColorId()); + points = new ArrayList<>(); + byte[] maskData = model.getMask(); + + for (int w = 0; w < imgWidth * sizeRatio; w++) { + for (int h = 0; h < imgHeight * sizeRatio; h++) { + + int realX = (int) (w / sizeRatio); + int realY = (int) (h / sizeRatio); + + int offset = imgWidth * realY + realX; + if (offset < maskData.length && maskData[offset] == 1) { + points.add(originPt.x + (float) w); + points.add(originPt.y + (float) h); + } + } + } + + float[] ptft = new float[points.size()]; + for (int j = 0; j < points.size(); j++) { + ptft[j] = points.get(j); + } + canvas.drawPoints(ptft, paintMask); + } else { + // 语义分割绘制 + drawSemanticMask(canvas, model); + } + } + } + + + super.onDraw(canvas); + } + + /** + * 语义分割基于mask不同值绘制颜色 + */ + private void drawSemanticMask(Canvas canvas, BasePolygonResultModel model) { + List colorPointsPairList = new ArrayList<>(); + for (int[] rgb : COLOR_MAP_DEF) { + Paint paint = new Paint(); + paint.setARGB(170, rgb[0], rgb[1], rgb[2]); + colorPointsPairList.add(new ColorPointsPair(paint)); + } + + byte[] maskData = model.getMask(); + for (int w = 0; w < imgWidth * sizeRatio; w++) { + for (int h = 0; h < imgHeight * sizeRatio; h++) { + + int realX = (int) (w / sizeRatio); + int realY = (int) (h / sizeRatio); + + int offset = imgWidth * realY + realX; + byte label = maskData[offset]; + + if (label >= colorPointsPairList.size()) { + for (int i = colorPointsPairList.size(); i <= label; i++ ) { + Paint newPaint = getRandomMaskPaint(i); + colorPointsPairList.add(new ColorPointsPair(newPaint)); + } + } + + colorPointsPairList.get(label).addPoint(originPt.x + (float) w); + colorPointsPairList.get(label).addPoint(originPt.y + (float) h); + } + } + + for (ColorPointsPair pair : colorPointsPairList) { + float[] points = pair.getPointsArray(); + if (points != null) { + canvas.drawPoints(points, pair.getPaint()); + } + } + } + + /** + * 用于分割模型,一个颜色对应一系列点 + */ + private class ColorPointsPair { + private final Paint paint; + private List points; + + public ColorPointsPair(Paint paint) { + this.paint = paint; + } + + public void addPoint(float point) { + if (points == null) { + points = new ArrayList<>(); + } + points.add(point); + } + + public float[] getPointsArray() { + if (points == null || points.size() == 0) { + return null; + } + float[] array = new float[points.size()]; + for (int i = 0; i < points.size(); i++) { + array[i] = points.get(i); + } + return array; + } + + public Paint getPaint() { + return paint; + } + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/Utils.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/Utils.java similarity index 99% rename from java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/Utils.java rename to java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/Utils.java index ef990237f..7f415f3db 100644 --- a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/Utils.java +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/Utils.java @@ -1,4 +1,4 @@ -package com.baidu.paddle.fastdeploy.app.ui; +package com.baidu.paddle.fastdeploy.app.ui.view; import android.content.Context; import android.content.res.Resources; diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/adapter/DetectResultAdapter.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/adapter/DetectResultAdapter.java new file mode 100644 index 000000000..5f854aed1 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/adapter/DetectResultAdapter.java @@ -0,0 +1,47 @@ +package com.baidu.paddle.fastdeploy.app.ui.view.adapter; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import com.baidu.paddle.fastdeploy.app.examples.R; +import com.baidu.paddle.fastdeploy.app.ui.util.StringUtil; +import com.baidu.paddle.fastdeploy.app.ui.view.model.BaseResultModel; + +import java.util.List; + +/** + * Created by ruanshimin on 2018/5/13. + */ + +public class DetectResultAdapter extends ArrayAdapter { + private int resourceId; + + public DetectResultAdapter(@NonNull Context context, int resource) { + super(context, resource); + } + + public DetectResultAdapter(@NonNull Context context, int resource, @NonNull List objects) { + super(context, resource, objects); + resourceId = resource; + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + BaseResultModel model = getItem(position); + View view = LayoutInflater.from(getContext()).inflate(resourceId, null); + TextView indexText = (TextView) view.findViewById(R.id.index); + TextView nameText = (TextView) view.findViewById(R.id.name); + TextView confidenceText = (TextView) view.findViewById(R.id.confidence); + indexText.setText(String.valueOf(model.getIndex())); + nameText.setText(String.valueOf(model.getName())); + confidenceText.setText(StringUtil.formatFloatString(model.getConfidence())); + return view; + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BasePolygonResultModel.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BasePolygonResultModel.java new file mode 100644 index 000000000..6b967c3b2 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BasePolygonResultModel.java @@ -0,0 +1,149 @@ +package com.baidu.paddle.fastdeploy.app.ui.view.model; + +import android.graphics.Point; +import android.graphics.Rect; + +import java.util.ArrayList; +import java.util.List; + +public class BasePolygonResultModel extends BaseResultModel { + private int colorId; + private boolean isRect; + private boolean isTextOverlay; + + private boolean isHasGroupColor = false; + private boolean isDrawPoints = false; + + + public boolean isTextOverlay() { + return isTextOverlay; + } + + public void setTextOverlay(boolean textOverlay) { + isTextOverlay = textOverlay; + } + + public int getColorId() { + return colorId; + } + + public void setColorId(int colorId) { + this.colorId = colorId; + } + + private byte[] mask; + + /** + * 是否是语义分割mask + */ + private boolean semanticMask; + + private Rect rect; + + BasePolygonResultModel() { + super(); + } + + BasePolygonResultModel(int index, String name, float confidence, Rect bounds) { + super(index, name, confidence); + parseFromRect(bounds); + } + + BasePolygonResultModel(int index, String name, float confidence, List bounds) { + super(index, name, confidence); + this.bounds = bounds; + } + + public Rect getRect() { + return rect; + } + + public Rect getRect(float ratio, Point origin) { + return new Rect((int) (origin.x + rect.left * ratio), + (int) (origin.y + rect.top * ratio), + (int) (origin.x + rect.right * ratio), + (int) (origin.y + rect.bottom * ratio)); + } + + private void parseFromRect(Rect rect) { + Point ptTL = new Point(rect.left, rect.top); + Point ptTR = new Point(rect.right, rect.top); + Point ptRB = new Point(rect.right, rect.bottom); + Point ptLB = new Point(rect.left, rect.bottom); + this.bounds = new ArrayList<>(); + this.bounds.add(ptTL); + this.bounds.add(ptTR); + this.bounds.add(ptRB); + this.bounds.add(ptLB); + this.rect = rect; + isRect = true; + } + + public boolean isRect() { + return isRect; + } + + public void setRect(boolean rect) { + isRect = rect; + } + + public byte[] getMask() { + return mask; + } + + public void setMask(byte[] mask) { + this.mask = mask; + } + + public void setSemanticMask(boolean semanticMask) { + this.semanticMask = semanticMask; + } + + public boolean isSemanticMask() { + return semanticMask; + } + + private List bounds; + + public List getBounds() { + return bounds; + } + + public List getBounds(float ratio, Point origin) { + List pointList = new ArrayList<>(); + for (Point pt : bounds) { + int nx = (int) (origin.x + pt.x * ratio); + int ny = (int) (origin.y + pt.y * ratio); + pointList.add(new Point(nx, ny)); + } + return pointList; + } + + public void setBounds(List bounds) { + this.bounds = bounds; + } + + public void setBounds(Rect bounds) { + parseFromRect(bounds); + } + + public boolean isHasMask() { + return (mask != null); + } + + public boolean isHasGroupColor() { + return isHasGroupColor; + } + + public void setHasGroupColor(boolean hasGroupColor) { + isHasGroupColor = hasGroupColor; + } + + public boolean isDrawPoints() { + return isDrawPoints; + } + + public void setDrawPoints(boolean drawPoints) { + isDrawPoints = drawPoints; + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BaseResultModel.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BaseResultModel.java new file mode 100644 index 000000000..5cab72c50 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/BaseResultModel.java @@ -0,0 +1,45 @@ +package com.baidu.paddle.fastdeploy.app.ui.view.model; + +/** + * Created by ruanshimin on 2018/5/16. + */ + +public class BaseResultModel { + private int index; + private String name; + private float confidence; + + public BaseResultModel() { + + } + + public BaseResultModel(int index, String name, float confidence) { + this.index = index; + this.name = name; + this.confidence = confidence; + } + + public float getConfidence() { + return confidence; + } + + public void setConfidence(float confidence) { + this.confidence = confidence; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/PoseViewResultModel.java b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/PoseViewResultModel.java new file mode 100644 index 000000000..3652903e3 --- /dev/null +++ b/java/android/app/src/main/java/com/baidu/paddle/fastdeploy/app/ui/view/model/PoseViewResultModel.java @@ -0,0 +1,11 @@ +package com.baidu.paddle.fastdeploy.app.ui.view.model; + +public class PoseViewResultModel extends BasePolygonResultModel { + public PoseViewResultModel() { + super(); + setRect(false); + setTextOverlay(false); + setDrawPoints(true); + } + +} diff --git a/java/android/app/src/main/res/layout/activity_main.xml b/java/android/app/src/main/res/layout/activity_main.xml index b6efea6d6..8fe754aa1 100644 --- a/java/android/app/src/main/res/layout/activity_main.xml +++ b/java/android/app/src/main/res/layout/activity_main.xml @@ -1,163 +1,14 @@ - + android:layout_height="match_parent"> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/java/android/app/src/main/res/layout/camera_page.xml b/java/android/app/src/main/res/layout/camera_page.xml new file mode 100644 index 000000000..b89cf725c --- /dev/null +++ b/java/android/app/src/main/res/layout/camera_page.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/android/app/src/main/res/layout/result_detect_item.xml b/java/android/app/src/main/res/layout/result_detect_item.xml new file mode 100644 index 000000000..6a2b09ebf --- /dev/null +++ b/java/android/app/src/main/res/layout/result_detect_item.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/android/app/src/main/res/layout/result_page.xml b/java/android/app/src/main/res/layout/result_page.xml new file mode 100644 index 000000000..a7b17c053 --- /dev/null +++ b/java/android/app/src/main/res/layout/result_page.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/android/app/src/main/res/layout/result_table_popview.xml b/java/android/app/src/main/res/layout/result_table_popview.xml new file mode 100644 index 000000000..681dfb42f --- /dev/null +++ b/java/android/app/src/main/res/layout/result_table_popview.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + . + + \ No newline at end of file diff --git a/java/android/app/src/main/res/values/strings.xml b/java/android/app/src/main/res/values/strings.xml index 1d8f1c2da..a2128791f 100644 --- a/java/android/app/src/main/res/values/strings.xml +++ b/java/android/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - FastDeploy PicoDet + EasyEdge FastDeploy PicoDet FastDeploy PP-OCRv2 @@ -17,6 +17,7 @@ LITE_POWER_HIGH 0.4 true + models/picodet_s_320_coco_lcnet labels/coco_label_list.txt @@ -35,4 +36,4 @@ 阈值控制 重新识别 保存结果 - \ No newline at end of file + diff --git a/java/android/app/src/main/res/values/values.xml b/java/android/app/src/main/res/values/values.xml new file mode 100644 index 000000000..156146d9a --- /dev/null +++ b/java/android/app/src/main/res/values/values.xml @@ -0,0 +1,17 @@ + + + 120dp + 46px + + 126px + 136px + + 46px + + 36px + + 15dp + + 15dp + + \ No newline at end of file