专栏首页CSDN博客在Android实现双目测距
原创

在Android实现双目测距

原文博客:Doi技术团队 链接地址:https://blog.doiduoyi.com/authors/1584446358138 初心:记录优秀的Doi技术团队学习经历 本文链接:在Android实现双目测距

前言

在上一章我们介绍了《双目摄像头测量距离》,在这个基础上,我们来了解如何在Android上使用双目测距算法。通过本教程,你不仅掌握如何在Android中使用SBM等双目测距算法,顺便也了解到如何在Android Studio配置OpenCV,通过使用OpenCV可以在Android中实现很多图像处理的功能。

配置OpenCV

下载OpenCV的Android版本源码,官网下载地址:https://opencv.org/releases/,如果读者无法下载,笔者也提供的源码下载,版本是3.4.1的,下载地址:https://resource.doiduoyi.com/#736y3wk

1、创建一个Android项目,解压源码压缩包,在Android Studio中点击File--->Import Model,然后浏览解压后的sdk/java添加,如下图所示,如何正常的话会显示OpenCV的版本。

在这里插入图片描述

2、复制OpenCV的动态库到app/libs目录下。

3、修改OpenCVLibrary的build.gradle的内容,这些内容全都都是app/build.gradle的内容,主要把applicationId去掉。

apply plugin: 'com.android.library'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        minSdkVersion 22
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

4、修改OpenCVLibrary的AndroidManifest.xml,内容大概如下,其中版本号对应自己导入的OpenCV的版本。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.opencv"
      android:versionCode="3410"
      android:versionName="3.4.1">

</manifest>

5、最后修改app/build.gradle的内容。

// 在android下添加以下代码
sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

// 在dependencies添加一下代码,根据情况修改版本号
implementation project(path: ':openCVLibrary341')

6、测试OpenCV,在应用中执行以下代码,如果初始化OpenCv成功,那配置OpenCV就已经成功了。

if (OpenCVLoader.initDebug()) {
    Log.d(TAG, "OpenCVLoader初始化成功");
}

双目测距

创建一个StereoBMUtil.java的java工具类,通过这类可以方便其他程序调用。在构造方法中配置StereoBM算法的一下参数,有些参数是相机标定的参数,具体用法参考《双目摄像头测量距离》这篇文章。 更加这篇教程,完成修改StereoBM算的相机标定的参数。

    public StereoBMUtil() {
        Mat cameraMatrixL = new Mat(3, 3, CvType.CV_64F);
        Mat distCoeffL = new Mat(5, 1, CvType.CV_64F);
        Mat cameraMatrixR = new Mat(3, 3, CvType.CV_64F);
        Mat distCoeffR = new Mat(5, 1, CvType.CV_64F);
        Mat T = new Mat(3, 1, CvType.CV_64F);
        Mat rec = new Mat(3, 1, CvType.CV_64F);
        // 【需要根据摄像头修改参数】左目相机标定参数 fc_left_x  0  cc_left_x  0  fc_left_y  cc_left_y  0  0  1
        cameraMatrixL.put(0, 0, 849.38718, 0, 720.28472, 0, 850.60613, 373.88887, 0, 0, 1);
        //【需要根据摄像头修改参数】左目相机标定参数 kc_left_01,  kc_left_02,  kc_left_03,  kc_left_04,   kc_left_05
        distCoeffL.put(0, 0, 0.01053, 0.02881, 0.00144, 0.00192, 0.00000);
        //【需要根据摄像头修改参数】右目相机标定参数 fc_right_x  0  cc_right_x  0  fc_right_y  cc_right_y  0  0  1
        cameraMatrixR.put(0, 0, 847.54814, 0, 664.36648, 0, 847.75828, 368.46946, 0, 0, 1);
        //【需要根据摄像头修改参数】右目相机标定参数 kc_right_01,  kc_right_02,  kc_right_03,  kc_right_04,   kc_right_05
        distCoeffR.put(0, 0, 0.00905, 0.02094, 0.00082, 0.00183, 0.00000);
        //【需要根据摄像头修改参数】T平移向量
        T.put(0, 0, -59.32102, 0.27563, -0.79807);
        // 【需要根据摄像头修改参数】rec旋转向量
        rec.put(0, 0, -0.00927, -0.00228, -0.00070);

        Size imageSize = new Size(imageWidth, imageHeight);
        Mat R = new Mat();
        Mat Rl = new Mat();
        Mat Rr = new Mat();
        Mat Pl = new Mat();
        Mat Pr = new Mat();
        Rect validROIL = new Rect();
        Rect validROIR = new Rect();
        Calib3d.Rodrigues(rec, R);                                   //Rodrigues变换
        //图像校正之后,会对图像进行裁剪,这里的validROI就是指裁剪之后的区域
        Calib3d.stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR, imageSize, R, T, Rl, Rr, Pl, Pr, Q, Calib3d.CALIB_ZERO_DISPARITY,
                0, imageSize, validROIL, validROIR);
        Imgproc.initUndistortRectifyMap(cameraMatrixL, distCoeffL, Rl, Pl, imageSize, CvType.CV_32FC1, mapLx, mapLy);
        Imgproc.initUndistortRectifyMap(cameraMatrixR, distCoeffR, Rr, Pr, imageSize, CvType.CV_32FC1, mapRx, mapRy);

        int blockSize = 18;
        int numDisparities = 11;
        int uniquenessRatio = 5;
        bm.setBlockSize(2 * blockSize + 5);                           //SAD窗口大小
        bm.setROI1(validROIL);                                        //左右视图的有效像素区域
        bm.setROI2(validROIR);
        bm.setPreFilterCap(61);                                       //预处理滤波器
        bm.setMinDisparity(32);                                       //最小视差,默认值为0, 可以是负值,int型
        bm.setNumDisparities(numDisparities * 16);                    //视差窗口,即最大视差值与最小视差值之差,16的整数倍
        bm.setTextureThreshold(10);
        bm.setUniquenessRatio(uniquenessRatio);                       //视差唯一性百分比,uniquenessRatio主要可以防止误匹配
        bm.setSpeckleWindowSize(100);                                 //检查视差连通区域变化度的窗口大小
        bm.setSpeckleRange(32);                                       //32视差变化阈值,当窗口内视差变化大于阈值时,该窗口内的视差清零
        bm.setDisp12MaxDiff(-1);
    }

创建一个compute()方法,该方法的参数是Bitmap类型的左右目摄像头的图像。compute()方法的返回值是图像计算图像结果转换的图像,这给图像可以很直观显示图像的距离。计算结果都存放在xyz矩阵中。

    public Bitmap compute(Bitmap left, Bitmap right) {
        Mat rgbImageL = new Mat();
        Mat rgbImageR = new Mat();
        Mat grayImageL = new Mat();
        Mat rectifyImageL = new Mat();
        Mat rectifyImageR = new Mat();
        Mat grayImageR = new Mat();
        //用于存放每个像素点距离相机镜头的三维坐标
        xyz = new Mat();
        Mat disp = new Mat();
        bitmapToMat(left, rgbImageL);
        bitmapToMat(right, rgbImageR);
        Imgproc.cvtColor(rgbImageL, grayImageL, Imgproc.COLOR_BGR2GRAY);
        Imgproc.cvtColor(rgbImageR, grayImageR, Imgproc.COLOR_BGR2GRAY);

        Imgproc.remap(grayImageL, rectifyImageL, mapLx, mapLy, Imgproc.INTER_LINEAR);
        Imgproc.remap(grayImageR, rectifyImageR, mapRx, mapRy, Imgproc.INTER_LINEAR);

        bm.compute(rectifyImageL, rectifyImageR, disp);                    //输入图像必须为灰度图
        Calib3d.reprojectImageTo3D(disp, xyz, Q, true);  //在实际求距离时,ReprojectTo3D出来的X / W, Y / W, Z / W都要乘以16
        Core.multiply(xyz, new Mat(xyz.size(), CvType.CV_32FC3, new Scalar(16, 16, 16)), xyz);

        // 用于显示处理
        Mat disp8U = new Mat(disp.rows(), disp.cols(), CvType.CV_8UC1);
        disp.convertTo(disp, CvType.CV_32F, 1.0 / 16);               //除以16得到真实视差值
        Core.normalize(disp, disp8U, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
        Imgproc.medianBlur(disp8U, disp8U, 9);
        Bitmap resultBitmap = Bitmap.createBitmap(disp8U.cols(), disp8U.rows(), Bitmap.Config.ARGB_8888);
        matToBitmap(disp8U, resultBitmap);
        return resultBitmap;
    }

执行上一步计算图像的距离之后,通过getCoordinate()方法可以获取图像中实际的三维坐标,结构是x, y, z

    public double[] getCoordinate(int dstX, int dstY) {
        double x = xyz.get(dstY, dstX)[0];
        double y = xyz.get(dstY, dstX)[1];
        double z = xyz.get(dstY, dstX)[2];
        return new double[]{x, y, z};
    }

又是上面的双目测距工具类,接下来就可以很方便实现双目测距。在MainActivity.java中,简单几步就完成了双目测距,在使用OpenCV之前一定要执行OpenCVLoader.initDebug(),然后读取assets文件夹中的图像,分别是是左右目拍摄保存的图像,把他们转化成Bitmap用于下一步执行距离计算。

//初始化
if (OpenCVLoader.initDebug()) {
      Log.d(TAG, "OpenCVLoader初始化成功");
}

// 加载图片
try {
    leftBitmap = BitmapFactory.decodeStream(getAssets().open("Left3.bmp"));
    rightBitmap = BitmapFactory.decodeStream(getAssets().open("Right3.bmp"));
    imageViewLeft.setImageBitmap(leftBitmap);
    imageViewRight.setImageBitmap(rightBitmap);
} catch (IOException e) {
    e.printStackTrace();
}

因为我们已经编写了一个StereoBMUtil工具类,在这里就可以直接计算这两张图像的物体距离了。计算完成之后,为了方便查看图像中的距离,把结果图在ImageView上显示,然后为ImageView添加点击获取坐标事件。用户在点击之后会获取到图像中的坐标,然后使用这个坐标从xyz中获取拍摄物体的实际三维坐标。

// 执行StereoBM算法
button.setOnClickListener(v -> {
    try {
        Bitmap result = stereoBMUtil.compute(leftBitmap, rightBitmap);
        imageViewResult.setImageBitmap(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
});

// 点击计算后的图片,获取三维坐标数据
imageViewResult.setOnTouchListener((v, event) -> {
    // 获取触摸点的坐标 x, y
    float x = event.getX();
    float y = event.getY();
    // 目标点的坐标
    float[] dst = new float[2];
    Matrix imageMatrix = imageViewResult.getImageMatrix();
    Matrix inverseMatrix = new Matrix();
    imageMatrix.invert(inverseMatrix);
    inverseMatrix.mapPoints(dst, new float[]{x, y});
    int dstX = (int) dst[0];
    int dstY = (int) dst[1];
    // 获取该点的三维坐标
    double[] c = stereoBMUtil.getCoordinate(dstX, dstY);
    String s = String.format("点(%d, %d) 三维坐标:[%.2f, %.2f, %.2f]", dstX, dstY, c[0], c[1], c[2]);
    Log.d(TAG, s);
    textView.setText(s);
    return true;
});

效果图如下:

在这里插入图片描述

使用摄像头测距

上面的是实现读取两张计算物体距离,并没有使用摄像头拍摄,那么接下来我们就通过使用Android设备接的双目摄像头,实时拍摄图像计算物体距离。创建一个新的Activity,命名为CameraActivity,按照通常的调用摄像头的方式,这样获取到的图像是左右目摄像头拍摄的图片拼接在一起的并且旋转的,我们需要的是把他们旋转回来并把他们裁剪分割,这样就可以获取到了两种分别是左右目摄像头拍摄的图像。

// 拍照获取左右摄像头的图像
button2.setOnClickListener(v -> {
    bgView.setVisibility(View.VISIBLE);
    ll.setVisibility(View.VISIBLE);
    Bitmap imgBitmap = mTextureView.getBitmap();
    Bitmap b = Utils.rotateBitmap(imgBitmap, 360 - sensorOrientation);
    List<Bitmap> bitmapList = Utils.bisectionBitmap(b);
    // 左右目摄像头的图像
    leftBitmap = bitmapList.get(0);
    rightBitmap = bitmapList.get(1);
    imageViewLeft.setImageBitmap(leftBitmap);
    imageViewRight.setImageBitmap(rightBitmap);
});

// 把图像翻转回来
public static Bitmap rotateBitmap(Bitmap bitmap, int angle) {
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

// 裁剪分割左右目图像
public static List<Bitmap> bisectionBitmap(Bitmap bitmap) {
    List<Bitmap> bitmapList = new ArrayList<>();
    Bitmap left = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth() / 2, bitmap.getHeight(), null, true);
    bitmapList.add(left);
    Bitmap right = Bitmap.createBitmap(bitmap, bitmap.getWidth() / 2, 0, bitmap.getWidth() / 2, bitmap.getHeight(), null, true);
    bitmapList.add(right);
    return bitmapList;
}

接下来的处理方式就跟之前的一样了,使用StereoBMUtil工具类读取分割后的左右目摄像头的图像执行计算,把结果图在ImageView上显示,然后为ImageView添加点击获取坐标事件。用户在点击之后会获取到图像中的坐标,然后使用这个坐标从xyz中获取拍摄物体的实际三维坐标。

// 执行StereoBM算法
button4.setOnClickListener(v -> {
    Bitmap result = stereoBMUtil.compute(leftBitmap, rightBitmap);
    imageViewResult.setImageBitmap(result);
});

// 点击计算后的图片,获取三维坐标数据
imageViewResult.setOnTouchListener((v, event) -> {
    // 获取触摸点的坐标 x, y
    float x = event.getX();
    float y = event.getY();
    float[] dst = new float[2];
    Matrix imageMatrix = imageViewResult.getImageMatrix();
    Matrix inverseMatrix = new Matrix();
    imageMatrix.invert(inverseMatrix);
    inverseMatrix.mapPoints(dst, new float[]{x, y});
    int dstX = (int) dst[0];
    int dstY = (int) dst[1];
    // 获取该点的三维坐标
    double[] c = stereoBMUtil.getCoordinate(dstX, dstY);
    String s = String.format("点(%d, %d) 三维坐标:[%.2f, %.2f, %.2f]", dstX, dstY, c[0], c[1], c[2]);
    Log.d(TAG, s);
    textView.setText(s);
    return true;
});

效果图如下:

在这里插入图片描述

本项目源码: https://resource.doiduoyi.com/#cosaa9o

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • opencv双目测距实现

    来自: http://blog.csdn.net/sunanger_wang/article/details/7744015 虽然最近注意力已经不可遏制地被神经...

    智能算法
  • 双目摄像头测量距离

    在计算机视觉中,可以通过双目摄像头实现,常用的有BM 算法和SGBM 算法等,双目测距跟激光不同,双目测距不需要激光光源,是人眼安全的,只需要摄像头,成本非常底...

    夜雨飘零
  • 双目视觉测距系统软硬件设计

    随着计算机技术和光电技术的发展,机器视觉技术应运而生。在图像处理技术领域中,有一种采用 CCD摄像机作为图像传感器采集数据的非接触式测量方法,这种方法具有精度高...

    苏州程序大白
  • 在Nexus上实现Ubuntu和Android 4.4.2 双启动

    现在在电话上测试Ubuntu变得更加简单,可以从Canonical上下载一个程序,它可以安装新的操作系统而不必删除原有的Andorid。能够双启动的系统可以吸引...

    用户8705033
  • Android Presentation实现双屏异显

    现在越来越多的Android设备有多个屏幕,双屏异显应用场景最多的应该就是类似于收银平台那种设备,在主屏上店员能够对点商品进行选择录入,副屏则是展示给我们的账单...

    砸漏
  • Android ToolBar 修改边距的实现方法

    如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

    砸漏
  • 【深度相机系列六】深度相机哪家强?附详细参数对比清单

    本文的深度相机制造商涉及:Microsoft、Intel、Leap Motion、Orbbec、图漾、Occipital Structure、Stereolab...

    用户1150922
  • Python 玩微信跳一跳

    这是一个 2.5D 插画风格的益智游戏,玩家可以通过按压屏幕时间的长短来控制这个「小人」跳跃的距离。可能刚开始上手的时候,因为时间距离之间的关系把握不恰当,只能...

    py3study
  • Android学习第七弹之手势操作

    在昨天我们讲了Android的OnTouch触摸事件,有时候触摸和手势是相互联系的,密不可分的关系,所以上节我们讲了触摸事件,今天我们自然而然的就需要讲手势操作...

    非著名程序员
  • 看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

    像网上其他将手势监听的博客一样,本文将以双击事件为引子,逐步展开探讨 Android 手势监听,你需要知道的点点滴滴,还是那句话:看完这篇还不会 Gesture...

    圆号本昊
  • Android P专区免费开放 -- 同样的Android,不同的体验

    原文链接:http://wetest.qq.com/lab/view/376.html

    WeTest质量开放平台团队
  • Android P专区免费开放 -- 同样的Android,不同的体验

    2018年3月8日,Google推出了Android P Preview版本,并提供官方镜像下载。

    WeTest质量开放平台团队
  • Android双重SurfaceView实现弹幕效果

    本文实例为大家分享了Android双重SurfaceView实现弹幕效果的具体代码,供大家参考,具体内容如下

    砸漏
  • [译] 如何在 Android 开发中充分利用多摄像头 API

    这篇博客是对我们的 Android 开发者峰会 2018 演讲 的补充,是与来自合作伙伴开发者团队中的 Vinit Modi、Android Camera PM...

    Android 开发者
  • View的滑动方式 详细介绍

    用于追踪手指滑动速度的。例如相册的图片,手指快速左右滑动会切换图片,慢则不会切换。获取速度前,要先调用computeCurrentVelocity计算速度,如下...

    胡飞洋
  • Android中View位置和触摸事件详解

    View是Android中所有控件的基类,不管是简单的Button和TextView,还是复杂的RelativeLayout和ListView,其基类都是Vie...

    砸漏
  • 带你解锁蓝牙skill(一)

    蓝牙这个专题,很值得深入研究,但又不是一篇两篇能说的清除,所以决定连载~~~ 不知道能坚持多久 在研究蓝牙源码之前,先来看看蓝牙大致都有什么功...

    fanfan
  • 在iOS中如何正确的实现行间距与行高

    最近准备给 VirtualView-iOS 的文本元素新增一个 lineHeight 属性,以便和 VirtualView-Android 配合时能更精确的保...

    用户1219438
  • 单目SLAM在移动端应用的实现难点有哪些?

    对优秀的回答进行了整理,解释权归答主所有,如有侵权请联系删除, 以下观点并不代表AI资源汇观点仅作为参考。

    小白学视觉

扫码关注云+社区

领取腾讯云代金券