Android SurfaceView onTouchEvent進階操作OpenCV顯示

  • 2019 年 10 月 8 日
  • 筆記

前一篇文章《Android SurfaceView onTouchEvent配合OpenCV顯示》介紹了Android SurfaceView中通過onTouchEvent事件點擊後在OpenCV中畫了個圓顯示出來,本身onTouchEvent還可以有按下,移動,抬起的捕獲,所以本篇我們在上一篇的基礎上做一下進階的顯示。

實現效果

通過點擊,移動在圖像上畫上矩形

★ 實現思路 ★

在OpenCV中畫矩形需要兩個坐標點即可,所以我們在點擊屏幕時傳遞給OpenCV一個啟始坐標點和一個結束坐標點,OpenCV中對每一幀的圖像的傳遞進來的兩個坐標點畫矩形即可。細分下來我們的步驟如下:

01

手指按下時記錄起始坐標和結束坐標相等

02

手指在滑動中更新結束坐標

03

手指抬起時傳遞一個標誌(這裡沒寫後面的,後面的我們會結合前面學的RecyclerView綜合使用)

01

VaccaeSurfaceView修改

在上章的Demo基礎上再加入一對新的點擊位置比例,這裡只計算位置的比例,在調用OpenCV時重新要甩這個比例來計算坐標點進行傳入。

上面為onTouchEvent事件,把手指按下、移動、抬起時的操作都進行了處理,代碼如下:

@Override  public boolean onTouchEvent(MotionEvent event) {        //獲取屏幕分辨率      DisplayMetrics metric=new DisplayMetrics();      windowManager.getDefaultDisplay().getMetrics(metric);        int width=metric.widthPixels;  // 寬度(PX)      int height=metric.heightPixels;  // 高度(PX)        int action = event.getAction();      switch (action){          case MotionEvent.ACTION_DOWN:              Log.e("surfaceviewtouch", "onTouch: down");              touchxscale=event.getRawX() / width;              touchyscale=event.getRawY() / height;              touchmovexscale=touchxscale;              touchmoveyscale=touchyscale;              istouch=false;              break;          case MotionEvent.ACTION_UP:              Log.e("surfaceviewtouch", "onTouch: up");              touchmovexscale=event.getRawX() / width;              touchmoveyscale=event.getRawY() / height;              istouch=true;              break;          case MotionEvent.ACTION_MOVE:              Log.e("surfaceviewtouch", "onTouch: move");              touchmovexscale=event.getRawX() / width;              touchmoveyscale=event.getRawY() / height;              istouch=false;              break;      }      return true;    }

上圖中調用OpenCV的方法nv21ToBitmap里我們重新計算了起始坐標和結束坐標的位置,然後新寫了一個JNI的方法進行調用,代碼如下:

private Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {      Bitmap bitmap=null;      try {          YuvImage image=new YuvImage(nv21, ImageFormat.NV21, width, height, null);          ByteArrayOutputStream stream=new ByteArrayOutputStream();          image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);          //將rawImage轉換成bitmap          BitmapFactory.Options options=new BitmapFactory.Options();          options.inPreferredConfig=Bitmap.Config.ARGB_8888;          bitmap=BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size(), options);            //加入圖像旋轉          Matrix m=new Matrix();          m.postRotate(rotatedegree);          bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),                  m, true);            //調用JNI方法處理圖像          if (!(touchxscale == 0 && touchyscale == 0 && touchmovexscale == 0 && touchmoveyscale == 0)) {              List<Point> points=new ArrayList<>();              touchx=(int) (bitmap.getWidth() * touchxscale);              touchy=(int) (bitmap.getHeight() * touchyscale);              points.add(new Point(touchx, touchy));              touchx=(int) (bitmap.getWidth() * touchmovexscale);              touchy=(int) (bitmap.getHeight() * touchmoveyscale);              points.add(new Point(touchx, touchy));                bitmap=VaccaeOpenCVJNI.Cameraframetouchgetbitbmp(bitmap, points, istouch);          }          stream.close();      } catch (IOException e) {          e.printStackTrace();      }      return bitmap;  }

02

VaccaeOpenCVJNI的修改

我們在VaccaeOpenCV的類中再加入一個新的方法Cameraframetouchgetbitbmp,參數為傳入的圖像,坐標的集合,還有一個是結束標誌。

03

native-lib.cpp的修改

在VaccaeOpenCV中的Cameraframetouchgetbitbmp中按ALT+ENTER後會在我們的native-lib.cpp中自動創建了對應的方法。

核心方法

像在OpenCV中畫圓,畫矩形我們最簡單的方法已經會,這裡主要就是看看傳進來的List<Point>我們怎麼取出來,在JNI中傳遞LIst集合,我們在《Android NDK編程(八)— JNI中List結構的類數據做為參數》中就已經學過,這裡正好在實戰中應用上了。

完整的Cameraframetouchgetbitbmp方法代碼

extern "C"  JNIEXPORT jobject JNICALL  Java_dem_vac_surfaceviewdemo_VaccaeOpenCVJNI_Cameraframetouchgetbitbmp(JNIEnv *env, jclass clazz,                                                                         jobject bmp, jobject points,                                                                         jboolean isovertouch) {      AndroidBitmapInfo bitmapInfo;      void *pixelscolor;      int ret;        //獲取圖像信息,如果返回值小於0就是執行失敗      if ((ret = AndroidBitmap_getInfo(env, bmp, &bitmapInfo)) < 0) {          LOGI("AndroidBitmap_getInfo failed! error-%d", ret);          return NULL;      }        //判斷圖像類型是不是RGBA_8888類型      if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {          LOGI("BitmapInfoFormat error");          return NULL;      }        //獲取圖像像素值      if ((ret = AndroidBitmap_lockPixels(env, bmp, &pixelscolor)) < 0) {          LOGI("AndroidBitmap_lockPixels() failed ! error=%d", ret);          return NULL;      }        //生成源圖像      cv::Mat src(bitmapInfo.height, bitmapInfo.width, CV_8UC4, pixelscolor);        //獲取ArrayList類引用      jclass list_jcls = env->FindClass("java/util/ArrayList");      if (list_jcls == NULL) {          LOGI("ArrayList沒找到相關類!");          return bmp;      }        //獲取ArrayList對象的get()的methodID      jmethodID list_get = env->GetMethodID(list_jcls, "get", "(I)Ljava/lang/Object;");      //獲取ArrayList對象的size()的methodID      jmethodID list_size = env->GetMethodID(list_jcls, "size", "()I");      //然後獲取我們的Point類的class      jclass jcls = env->FindClass("android/graphics/Point");      if (jcls == NULL) {          return bmp;      }      //獲取Point的值      jfieldID pointx = env->GetFieldID(jcls, "x", "I");      jfieldID pointy = env->GetFieldID(jcls, "y", "I");        //獲取集合的里的個數      int size= env->CallIntMethod(points,list_size);        //定義開始和結束的Point      cv::Point pointstart;      cv::Point pointend;      //只取第一個啟始點和最後一個結束點      if (size < 1) {          return bmp;      } else {          //獲取啟始點的Point          jobject item = env->CallObjectMethod(points, list_get, 0);          pointstart = cv::Point(env->GetIntField(item, pointx), env->GetIntField(item, pointy));            //獲取結束點的Point          item = env->CallObjectMethod(points, list_get, size - 1);          pointend = cv::Point(env->GetIntField(item, pointx), env->GetIntField(item, pointy));      }        //畫矩形框      cv::rectangle(src, pointstart, pointend, cv::Scalar(255, 0, 0),3);        //獲取原圖片的參數      jclass java_bitmap_class = (jclass) env->FindClass("android/graphics/Bitmap");      jmethodID mid = env->GetMethodID(java_bitmap_class, "getConfig",                                       "()Landroid/graphics/Bitmap$Config;");      jobject bitmap_config = env->CallObjectMethod(bmp, mid);      //將SRC轉換為圖片      jobject _bitmap = mat2bitmap(env, src, false, bitmap_config);        AndroidBitmap_unlockPixels(env, bmp);        return _bitmap;  }  

下圖就是手指按下後,移動中畫矩形的圖像效果

-END-