1. 介绍
通过OpenCV
实现,实时识别摄像头中的固定颜色块的坐标位置,并进行标注。
简而言之,追踪摄像头中纯色物体的运动轨迹。
我们可以通过OpenCV
来识别视频中的纯色物体的移动轨迹。
利用了openCV
中的ColorBlobDetector
功能。
2. 实现
步骤比较简单:
- 获取摄像头拍摄数据,得到ImageProxy 并转为Mat进行计算。
- Android 拍摄的图片默认为rgba格式,将该格式转为HSV。
- 使用Core.inRange() 将指定颜色范围内的色块从图片中分割出来。
- 进行膨胀处理,可以使用morphologyEx 也可以使用dilate。
- 针对膨胀完毕的数据,执行轮廓提取。
- 遍历轮廓数组得到轮廓面积最大的坐标集合。
- 完成
主要步骤为上面这几种。下面,将会介绍如何实现。
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
方法进行
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_OPEN, kernel);//进行开运算
还可以直接通过dilate
进行:
Imgproc.dilate(dst, dst, new Mat());
运算完毕之后,得到的dst对象就是处理结果了。
2.5 轮廓提取
当我们得到膨胀结束后的对,就可以进行轮廓提取findContours
了。示例:
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示例代码。