OpenCV: 检测颜色并在颜色上绘制线条

3
我的应用将计算电池的弹跳高度,我打算使用蓝色条纹来定义“基准”,然后计算它与电池之间的像素距离。
我要如何检测到蓝色并在蓝色纸条的底部绘制一条线,以便使用该线进行像素距离计算?
我知道OpenCV有一个blob检测应用程序,可以绘制所选颜色周围的轮廓,但我需要应用程序自动检测颜色并向我提供其坐标,以便我可以应用。
canvas.drawLine(0, 0, 20, 20, p);

画线的操作是在从视频中提取出来的位图图像上完成的。

enter image description here

编辑: 当我测试它时,它不能检测到蓝色。我甚至在有蓝色和绿色纸张的图片上进行了测试,但输出没有检测到蓝色...

这是我的图片: 输出output 输入input 这是我的当前代码:

要划线。

Mat hsvMat = new Mat();
            //Mat black_hue_range = new Mat();
            //Core.inRange(hsvMat, new Scalar(0, 0, 0), new Scalar(180, 255, 30, 0), black_hue_range);
            Mat blue_hue = new Mat();
            Scalar lower_blue = new Scalar(110,50,50);
            Scalar upper_blue = new Scalar(130,255,255);

            //Convert BGR to HSV
            Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_BGR2HSV);

            //Threshold the HSV image to get only blue colors
            Core.inRange(hsvMat, lower_blue, upper_blue, blue_hue); // hue == a colour or shade
            Mat tempMat22 = new Mat();
            Core.bitwise_and(hsvMat,hsvMat,tempMat22,blue_hue);
            Utils.matToBitmap(tempMat22, b);

            //Bitmap mutableBitmap = b.copy(Bitmap.Config.ARGB_8888, true);
            imgR.setImageBitmap(b);

编辑:

下面的代码返回了三个值,我假设是 H = data [0],S data [1],V = data [2] 现在我有了HSV值,怎么得到上限和下限呢?亚历山大·雷诺兹在这里给出的答案似乎是针对RGB而不是HSV。注意:我现在读取的颜色像素是绿色而不是蓝色。

E/data: H:90.0 S:113.0 V:144.0

if (getIntent().hasExtra("byteArray")) {

            bitmap = BitmapFactory.decodeByteArray(getIntent().getByteArrayExtra("byteArray"), 0, getIntent().getByteArrayExtra("byteArray").length);

            int width= bitmap.getWidth();
            int height=bitmap.getHeight();

            int centerX=width/2;
            int centerY=height/2;
            srcMat = new Mat();
            Utils.bitmapToMat(bitmap, srcMat);
            Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_BGR2HSV);
            srcMat.convertTo(srcMat, CvType.CV_64FC3); //http://answers.opencv.org/question/14961/using-get-and-put-to-access-pixel-values-in-java/
            double[] data = srcMat.get(centerX, centerY);
            Log.e("data", String.valueOf("H:"+data[0]+" S:"+data[1]+" V:"+data[2]));
            Log.e("dlength", String.valueOf(data.length));
            Mat matHSV = new Mat(0,0,CvType.CV_64FC3);

通过添加以下三行代码,我会收到一个错误,说位图== null,所以我不确定像素读取是否有效。
matHSV.put(0,0,data);
Utils.matToBitmap(matHSV, bb);
imgDisplay.setImageBitmap(bb);

编辑2:

当尝试使用Rect指定roi时,我遇到了一个错误:

由CvException引起[org.opencv.core.CvException:cv :: Exception:/build/master_pack-android/opencv/modules/core/src/matrix.cpp:483:error:(-215)0 <= _rowRange.start && _rowRange.start <= _rowRange.end && _rowRange.end <= m.rows在函数cv :: Mat :: Mat中(const cv :: Mat&,const cv :: Range&,const cv :: Range&)

bitmap = globals.getBmp();
        Mat srcMat = new Mat();
        Utils.bitmapToMat(bitmap, srcMat);

        Mat hsvMat = new Mat();
        Imgproc.cvtColor(srcMat,hsvMat,Imgproc.COLOR_BGR2HSV);

    Mat roiMat;
            Rect rectangle = new Rect(177,1571,822,1680);// 177,1571(top right corner),   820,1680 (btm right) 820, 1565(topright)
            roiMat = new Mat(hsvMat,rectangle);
            Utils.matToBitmap(roiMat, temp);

            ImageView imageView = (ImageView) findViewById(R.id.imageView);
            imageView.setImageBitmap(temp);

我还尝试使用 Range

 Range rowRange = new Range(177, 822);
        Range colRange = new Range(1571, 1680);
        roiMat = new Mat(hsvMat, rowRange, colRange); // public Mat(Mat m, Range rowRange, Range colRange)

EDIT2.5:

changing:

roiMat = new Mat(hsvMat, rowRange, colRange);

to:

roiMat = new Mat(hsvMat, colRange, rowRange); 

似乎已经解决了问题,但现在报错显示我的bmp为空。

java.lang.IllegalArgumentException: bmp == null

编辑3: 最终成功转换了Alexander Reynolds回答的Python代码, 但我似乎无法查看结果,因为出现了错误:

java.lang.IllegalArgumentException: bmp == null

位于

Utils.matToBitmap(idk,temp);

bitmap = cn.getBmp();
    Mat srcMat = new Mat();
    Utils.bitmapToMat(bitmap, srcMat);

    Mat hsvMat = new Mat();
    Imgproc.cvtColor(srcMat,hsvMat,Imgproc.COLOR_BGR2HSV);

    Mat roiMat;
    Rect rectangle = new Rect(177,1571,822,1680);// 177,1571(top right corner),   820,1680 (btm right) 820, 1565(topright)
    Range rowRange = new Range(177, 822);
    Range colRange = new Range(1571, 1680);
    roiMat = new Mat(hsvMat, colRange, rowRange); // public Mat(Mat m, Range rowRange, Range colRange)

    MatOfDouble mu = new MatOfDouble();
    MatOfDouble sig = new MatOfDouble();

    Core.meanStdDev(roiMat,mu,sig);


    double m = mu.get(0,0)[0];
    double d = sig.get(0,0)[0];
    int a = 9;
    Log.e("m , d", "m "+String.valueOf(m)+" d"+String.valueOf(d));
    Mat blue_mask = new Mat();
    Core.inRange(hsvMat, new Scalar(m-a*d), new Scalar(m+a*d), blue_mask); // javadoc: inRange(src, lowerb, upperb, dst)
    Mat idk = new Mat();
    Core.bitwise_and(hsvMat,hsvMat,idk,blue_mask);
    Utils.matToBitmap(idk,temp);
    Bitmap mutableBitmap = temp.copy(Bitmap.Config.ARGB_8888, true);
1个回答

4
你可以使用内置的OpenCV方法inRange()执行颜色过滤,这将允许您创建一个遮罩,其中包含任何像素值介于定义的下限和上限之间的白色,并且否则为黑色。从那里,您可以简单地找到遮罩中白色像素的位置。
请参考本教程获取示例。
此外,我之前的回答here提供了一些寻找良好下限和上限的建议——特别是在图像中找到一个你知道的区域(比如链接中的那个)有你想要的颜色,并找出这些值的平均值和标准差(在任何颜色空间中,但我可能建议从HSV或HLS开始)。然后,您可以将下限轻松设置为mean-a*stddev,将上限设置为mean+b*stddev,其中ab是您可以尝试使用的某些值,以查看哪些最适合选择蓝色(仅蓝色)。您可以从a=b开始,并对它们使用整数值(1、2、3等),然后逐步调整。
一旦你有了面具,很可能会在其中有几个孔或图像其他地方的多余白色像素。您可以使用轮廓检测、霍夫线检测或Blob检测来获取正确的矩形。在这种情况下,我建议对带有findContours()的掩码进行轮廓检测,找到最大的轮廓,找到boundingRect并围绕它,这将直接给出像素位置。

Python示例

你正在使用C++编程,但是为了提供示例,Python更快一些,因此你需要进行翻译。我将使用你的第一张图片的缩小版本:

Starting image

import numpy as np
import cv2

img = cv2.imread('image.jpg')
img = cv2.resize(img, None, fx=0.1, fy=0.1)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
roi = hsv[430:450, 20:170]

这里我只是调整了图像的大小(主要是为了方便显示),转换了颜色空间,并定义了一个感兴趣的区域(ROI),其中只包括蓝色像素。在BGR中,ROI看起来像这样:

Blue ROI

这个 ROI 只包含蓝色像素,因此我可以找到蓝色值的平均值和标准差,并将它们用作 inRange() 函数的值。
mu, sig = cv2.meanStdDev(roi)
a = 9

blue_mask = cv2.inRange(hsv, mu-a*sig, mu+a*sig)

因此,我们只有蓝色值的掩码:

Blue mask

从这里开始,您可以进行常规的轮廓检测,并找到其周围的边界框:

_, contours, _ = cv2.findContours(blue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)

现在我们有了一个包围试纸的矩形框(我们知道左上角的位置、宽度和高度)在原始图像中:

Detected blue


嘿,@Alexander Reynolds,我已将图像转换为HSV并使用inRange检测蓝色,但输出似乎不正确。我已编辑问题以反映我所做的更改。 - Tix
@Tix 很难确定问题出在哪里...你是怎么得出蓝色值的呢? - alkasm
嗨,@Alexander Reynolds,我在OpenCV颜色空间教程中找到了它。 - Tix
@Tix 嗯,你不能只是复制他们的相同蓝色数值,他们定义了一定的像素范围,而你的蓝色与他们的差别很大。相反,尝试裁剪只有蓝色的部分,并检查数值是什么。正如我之前提到的,你可以计算一定范围内所有蓝色像素的平均值和标准差。 - alkasm
@Tix 我不知道那是什么意思,而且那也不是错误信息。如果你在某个任务中遇到了具体的问题,请查阅文档,如果找不到答案,请发布一个新的问题。我已经给出了原始问题的工作流程。 - alkasm
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接