Android OpenCV 4.6.0 颜色追踪

2022-12-07 17:34:54 浏览数 (1)

1. 介绍

通过OpenCV实现,实时识别摄像头中的固定颜色块的坐标位置,并进行标注。

简而言之,追踪摄像头中纯色物体的运动轨迹。

我们可以通过OpenCV来识别视频中的纯色物体的移动轨迹。

利用了openCV中的ColorBlobDetector功能。

2. 实现

步骤比较简单:

  1. 获取摄像头拍摄数据,得到ImageProxy 并转为Mat进行计算。
  2. Android 拍摄的图片默认为rgba格式,将该格式转为HSV。
  3. 使用Core.inRange() 将指定颜色范围内的色块从图片中分割出来。
  4. 进行膨胀处理,可以使用morphologyEx 也可以使用dilate。
  5. 针对膨胀完毕的数据,执行轮廓提取。
  6. 遍历轮廓数组得到轮廓面积最大的坐标集合。
  7. 完成

主要步骤为上面这几种。下面,将会介绍如何实现。

2.1 调用摄像头获取Mat

Android CameraX 的初始化就不多赘述了。网上有不少的示例。

我们可以不用预览功能,而单纯使用ImageAnalysis分析功能。

得到ImageProxy对象,然后将ImageProxy对象直接转为Mat对象。

转换方法可以参考:Android ImageProxy 转 OpenCV Mat对象

有完整的介绍。包括图片方向的矫正。

当我们得到Mat后就会开始进行下面的操作了。

PS:本来打算用VideoCapture对象的,但是老是崩溃错误。没办法。

2.2 转HSV

Android拍摄的照片颜色是RGBa格式的。我们需要将该格式转为HSV才能进行下一步。

代码语言:javascript复制
Mat hsv = new Mat();
Imgproc.cvtColor(mat, hsv, Imgproc.COLOR_RGB2HSV_FULL); //颜色通道转换

将得到的mat对象传进去,然后使用:Imgproc.COLOR_RGB2HSV_FULL选项进行转换。

可以将得到的hsv对象转为Bitmap,扔给ImageView进行显示,下面的每个步骤都可以将得到的Mat进行显示,这样我们可以了解整个转换过程中的效果。

2.3 inRange 色块提取

我们转换完毕的HSV格式的Mat对象,可以直接进行色块提取。示例:

代码语言:javascript复制
Mat dst = new Mat();
//颜色检查的上限和下限
Core.inRange(hsv, new Scalar(0,140,121), new Scalar(30,255,255), dst);

其中的两个Scalar 是hsv格式的颜色对象。

第一个是开始值,后面的是结束值。然后openCV就会在这两个颜色范围内进行分割。将属于该颜色范围的地方设置为白色。

不属于的就设置为黑色。

而具体里面的参数应该写多少,就根据大家实际需要采集的颜色进行判断了。

给几个示例:

代码语言:javascript复制
Core.inRange(hsv, Scalar(30, 40, 50), Scalar(40, 255, 255), dsty);  //黄色
Core.inRange(hsv, Scalar(45, 55, 55), Scalar(90, 255, 255), dstg);  //绿色
Core.inRange(hsv, Scalar(0,180,121), Scalar(30, 255, 255), dsto);  //橙色

大家根据自己的实际需求,可以通过HSV颜色卡,设置不同的颜色。

PS:实在没办法,也可以通过openCV的 samples工程中的 color-blob-detection 示例代码。实现点击触摸获取当前图片的HSV颜色值。 ColorBlobDetector 类中,下面的方法可以打印看看结果值。

代码语言:javascript复制
 public void setHsvColor(Scalar hsvColor) {
        double minH = (hsvColor.val[0] >= mColorRadius.val[0]) ? hsvColor.val[0]-mColorRadius.val[0] : 0;
        double maxH = (hsvColor.val[0] mColorRadius.val[0] <= 255) ? hsvColor.val[0] mColorRadius.val[0] : 255;

        mLowerBound.val[0] = minH;
        mUpperBound.val[0] = maxH;

        mLowerBound.val[1] = hsvColor.val[1] - mColorRadius.val[1];
        mUpperBound.val[1] = hsvColor.val[1]   mColorRadius.val[1];

        mLowerBound.val[2] = hsvColor.val[2] - mColorRadius.val[2];
        mUpperBound.val[2] = hsvColor.val[2]   mColorRadius.val[2];

        mLowerBound.val[3] = 0;
        mUpperBound.val[3] = 255;

        Mat spectrumHsv = new Mat(1, (int)(maxH-minH), CvType.CV_8UC3);

        for (int j = 0; j < maxH-minH; j  ) {
            byte[] tmp = {(byte)(minH j), (byte)255, (byte)255};
            spectrumHsv.put(0, j, tmp);
        }
//        System.out.println("颜色值 低值:"  Arrays.toString(mLowerBound.val));
//        System.out.println("颜色值 高值:"  Arrays.toString(mUpperBound.val));
        Imgproc.cvtColor(spectrumHsv, mSpectrum, Imgproc.COLOR_HSV2RGB_FULL, 4);
    }

2.4 膨胀

我们执行完毕inRange方法后,就能得到色块了。但是复杂环境下会有一些噪点。我们可以通过膨胀算法进行降噪。

有两种方法:1 通过morphologyEx进行。2 通过dilate方法进行

代码语言:javascript复制
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_OPEN, kernel);//进行开运算

还可以直接通过dilate进行:

代码语言:javascript复制
Imgproc.dilate(dst, dst, new Mat());

运算完毕之后,得到的dst对象就是处理结果了。

2.5 轮廓提取

当我们得到膨胀结束后的对,就可以进行轮廓提取findContours了。示例:

代码语言:javascript复制
List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); //存储提取后的轮廓对象集合
Mat hireachy = new Mat();//起到一个提取过程中间转换暂存的作用。
Imgproc.findContours(dst, contours, hireachy, Imgproc.RETR_EXTERNAL,
                Imgproc.CHAIN_APPROX_SIMPLE); //执行轮廓提取

执行完毕后,contours集合中就存储了各种坐标了。

2.6 最大面积区域提取

我们可以根据区域面积进行区分哪些是我们需要的。哪些是误判的。通常面积最大的就应该是我们需要的物体了。

代码语言:javascript复制
// 查找最大面积
double maxArea = 0;
Iterator<MatOfPoint> each = contours.iterator();
Rect rect = null;
while (each.hasNext()) {
   MatOfPoint wrapper = each.next();
   double area = Imgproc.contourArea(wrapper);
   if (area > maxArea) {
       maxArea = area;
       rect = Imgproc.boundingRect(wrapper);//将该区域转为Rect矩形对象
   }
}

上面示例中,我是将面积最大的数据,转为了矩形对象。

如果不想转矩形对象。那么可以转为MatOfPoint对象。它绘制的时候将会是多边形,示例:

代码语言:javascript复制
 // 查找最大面积
double maxArea = 0;
Iterator<MatOfPoint> each = contours.iterator();
while (each.hasNext()) {
    MatOfPoint wrapper = each.next();
    double area = Imgproc.contourArea(wrapper);
    if (area > maxArea){
        maxArea = area;
    }   
}

List<MatOfPoint> mContours = new ArrayList<MatOfPoint>();
each = contours.iterator();
while (each.hasNext()) {
        MatOfPoint contour = each.next();
        if (Imgproc.contourArea(contour) > 0.1*maxArea) {
           mContours.add(contour);
        }
}

我们可以将数据过滤,得到MatOfPoint对象,也可以是Rect对象。

下一步,就是绘制该对象了

2.7 绘制提取区域

我们得到的数据可能为空,所以要进行判断,如果不为空,那么就绘制一个红色边框的矩形。边框宽度为2。

代码语言:javascript复制
//得到最大的对象
if (rect != null) {
   Imgproc.rectangle(mat, rect, new Scalar(255, 0, 0), 2); //在mat中绘制一个矩形
}

我们如果是MatOfPoint对象,绘制方法如下:

代码语言:javascript复制
Imgproc.drawContours(mat, mContours, -1,  new Scalar(255, 0, 0),2);

2.8 Mat 转 Bitmap

到这一步的时候,就可以将mat转为Bitmap,并给到ImageView进行显示了。OpenCV提供了转换工具:

代码语言:javascript复制
Bitmap bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mat, bitmap);
imageView.setImageBitmap(bitmap); //根据自己的ImageView进行替换就可以了。

2.9 release Mat对象

我们在上面的步骤中大量创建和使用了Mat对象,要注意Mat对象的销毁。如果使用完毕后,需要主动调用mat.release()进行释放对象哦,否则会占用资源。

3. 小结

到这里追踪效果就实现了,我们也可以拿到实时的坐标数据进行其他的业务计算了。

整体实现的代码大部分参考openCV SDK中的samples示例代码。

0 人点赞