尝试使用 opencv 从图像中检测蓝色,并得到意想不到的结果
Trying to detect blue color from image using opencv, and getting unexpected result
我是 OpenCV4Android 新手。这是我编写的一些代码,用于检测图像中的蓝色斑点。在以下图像中,图像 1 在我的笔记本电脑中。我 运行 应用程序和 OpenCV 相机捕获的帧是图像 2。您可以查看代码以了解其余图像是什么。 (在代码中可以看到,所有图片都保存在SD卡中。)
我有以下问题:。
为什么摄像头拍摄的rgba帧中淡蓝色的斑点颜色变成了淡黄色(如图2)
我在最大的蓝色斑点周围创建了一个 boundingRect
,然后 ROI
通过 rgbaFrame.submat(detectedBlobRoi)
。但是你可以在最后一张图片中看到,它看起来就像几个灰色像素。 我期待蓝色球体与图像的其余部分分开。
我错过了什么或做错了什么?
代码:
private void detectColoredBlob () {
Highgui.imwrite("/mnt/sdcard/DCIM/rgbaFrame.jpg", rgbaFrame);//check
Mat hsvImage = new Mat();
Imgproc.cvtColor(rgbaFrame, hsvImage, Imgproc.COLOR_RGB2HSV_FULL);
Highgui.imwrite("/mnt/sdcard/DCIM/hsvImage.jpg", hsvImage);//check
Mat maskedImage = new Mat();
Scalar lowerThreshold = new Scalar(170, 0, 0);
Scalar upperThreshold = new Scalar(270, 255, 255);
Core.inRange(hsvImage, lowerThreshold, upperThreshold, maskedImage);
Highgui.imwrite("/mnt/sdcard/DCIM/maskedImage.jpg", maskedImage);//check
Mat dilatedMat= new Mat();
Imgproc.dilate(maskedImage, dilatedMat, new Mat() );
Highgui.imwrite("/mnt/sdcard/DCIM/dilatedMat.jpg", dilatedMat);//check
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(dilatedMat, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//Use only the largest contour. Other contours (any other possible blobs of this color range) will be ignored.
MatOfPoint largestContour = contours.get(0);
double largestContourArea = Imgproc.contourArea(largestContour);
for ( int i=1; i<contours.size(); ++i) {//NB Notice the prefix increment.
MatOfPoint currentContour = contours.get(0);
double currentContourArea = Imgproc.contourArea(currentContour);
if (currentContourArea > largestContourArea) {
largestContourArea = currentContourArea;
largestContour = currentContour;
}
}
Rect detectedBlobRoi = Imgproc.boundingRect(largestContour);
Mat detectedBlobRgba = rgbaFrame.submat(detectedBlobRoi);
Highgui.imwrite("/mnt/sdcard/DCIM/detectedBlobRgba.jpg", detectedBlobRgba);//check
}
- 计算机中的原始图像,这是通过将 phone 的相机放在笔记本电脑屏幕前拍摄的。
- rgbaFrame.jpg
- hsvImage.jpg
- dilatedImage.jpg
- maskedMat.jpg
- detectedBlobRgba.jpg
编辑:
我刚刚使用了 Core.inRange(hsvImage, new Scalar(0,50,40), new Scalar(10,255,255), maskedImage);//3, 217, 225 --- 6, 85.09, 88.24 ...... 3 219 255
,我通过给网站 colorizer.org 一个自定义的红色 HSV 值(即 OpenCV 红色 Scalar(3, 217, 255)
(其中落在给定 inRange
函数中设置的范围内,我将通道值缩放到 colorizer.org 的比例,即 H=0-360,S=0-100,V=0-100,通过将 H 值乘以 2,并将 S 和 V 值都除以 255 再乘以 100。这给了我 6, 85.09, 88.24
我在网站上设置的,并截取了屏幕截图(下图中的第一个)。
- 原始截图,我截取了这一帧。
- rgbaFrame.jpg
- hsvImage.jpg
- maskedImage.jpg
- dilatedMat.jpg
- detectedBlobRgba.jpg
重要提示:
当我触摸 rgbaFrame 内部时,我的测试应用程序实际调用了给定的方法(即它在 onTouch
方法内部调用)。我还使用以下代码将我触摸过的彩色斑点的 Hue
、Saturation
和 Value
值打印到 TextView
。 当我 运行 这个应用程序时,我触摸了红色的斑点,得到了以下值:Hue:3, Saturation:219, Value:255
.
public boolean onTouch(View v, MotionEvent motionEvent) {
detectColoredBlob();
int cols = rgbaFrame.cols();
整数行 = rgbaFrame.rows();
int xOffset = (openCvCameraBridge.getWidth() - cols) / 2;
int yOffset = (openCvCameraBridge.getHeight() - rows) / 2;
int x = (int) motionEvent.getX() - xOffset;
int y = (int) motionEvent.getY() - yOffset;
Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");//check
if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) { return false; }
Rect touchedRect = new Rect();
touchedRect.x = (x > 4) ? x - 4 : 0;
touchedRect.y = (y > 4) ? y - 4 : 0;
touchedRect.width = (x + 4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x;
touchedRect.height = (y + 4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y;
Mat touchedRegionRgba = rgbaFrame.submat(touchedRect);
Mat touchedRegionHsv = new Mat();
Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);
double[] channelsDoubleArray = touchedRegionHsv.get(0, 0);//**********
float[] channelsFloatArrayScaled = new float[3];
for (int i = 0; i < channelsDoubleArray.length; i++) {
if (i == 0) {
channelsFloatArrayScaled[i] = ((float) channelsDoubleArray[i]) * 2;// TODO Wrap an ArrayIndexOutOfBoundsException wrapper
} else if (i == 1 || i == 2) {
channelsFloatArrayScaled[i] = ((float) channelsDoubleArray[i]) / 255;// TODO Wrap an ArrayIndexOutOfBoundsException wrapper
}
}
int androidColor = Color.HSVToColor(channelsFloatArrayScaled);
view.setBackgroundColor(androidColor);
textView.setText("Hue : " + channelsDoubleArray[0] + "\nSaturation : " + channelsDoubleArray[1] + "\nValue : "
+ channelsDoubleArray[2]);
touchedRegionHsv.release();
return false; // don't need subsequent touch events
}
可能您使用的蓝色范围是错误的,在 OpenCV 中,色调范围是 0-180,而您给定的是 170-270。找到正确的蓝色色调值并在 inRange 中使用。
- http://answers.opencv.org/question/30547/need-to-know-the-hsv-value/#30564
- http://answers.opencv.org/question/28899/correct-hsv-inrange-values-for-red-objects/#28901
您可以参考此处的答案以选择正确的 hsv 值。
下面是分割红色的代码,和你的代码核对一下,确保分割的是红色物体。
Imgproc.cvtColor(rgbaFrame, hsv, Imgproc.COLOR_RGB2HSV,4); // Convert to hsv for color segmentation.
Core.inRange(hsv,new Scalar(0,50,40,0), new Scalar(10,255,255,0),thr);//upper red range of hue cylinder
在将图像转换为 HSV 颜色 space 和使用 HSV 颜色 space 时存在多个陷阱。
OpenCV 使用压缩的色调范围,因为原始色调范围从 0 到 360,这意味着值不能适合 1 个字节(值 0 到 255),而饱和度和值通道正好被1个字节覆盖。因此,OpenCV 使用色调值除以 2。因此色调通道将被 0 到 180 之间的矩阵条目覆盖。关于这一点,您的色调范围从 170 到 270 应该除以 2 = OpenCV 中的范围 65 到 135。
色调告诉您色调,但饱和度和明度对于减少噪点仍然很重要,因此也将您的阈值设置为某个最小饱和度和明度
非常重要:OpenCV 使用 BGR 内存排序进行渲染和图像保存。这意味着如果您的图像具有 RGB(a) 排序并且您在没有进行颜色转换的情况下保存它,那么您将交换 R 和 B 通道,因此假设红色将变为蓝色等。不幸的是通常您无法从图像数据本身读取,无论是它是RGB或BGR排序的,所以你应该尝试从图像源中找到它。 OpenCV 允许多个标志从 RGB(A) 转换为 HSV and/or 从 BGR(A) 转换为 HSV,and/or 从 RGB 转换为 BGR 等,所以这没问题,只要你知道哪个图像使用的内存格式。但是,显示和保存始终采用 BGR 顺序,因此如果要显示或保存图像,请将其转换为 BGR!然而,无论您是使用 BGR2HSV 转换 BGR 图像还是使用 RGB2HSV 转换 RGB 图像,HSV 值都是相同的。但是如果你用 RGB2HSV 转换 BGR 图像或用 BGR2HSV 转换 RGB 图像,它会有错误的值......我不是 100% 确定 Java/Python/Android openCV 的 API,但你的图像看起来真的像 B 和 R 通道被交换或误解(但由于您使用 RGBA2HSV 转换,因此 hsv 颜色没有问题)。
关于您的轮廓提取,您的代码中有一个小错误(复制粘贴?),每个人都可能偶尔会观察到:
MatOfPoint largestContour = contours.get(0);
double largestContourArea = Imgproc.contourArea(largestContour);
for ( int i=1; i<contours.size(); ++i) {//NB Notice the prefix increment.
// HERE you had MatOfPoint currentContour = contours.get(0); so you tested the first contour in each iteration
MatOfPoint currentContour = contours.get(i);
double currentContourArea = Imgproc.contourArea(currentContour);
if (currentContourArea > largestContourArea) {
largestContourArea = currentContourArea;
largestContour = currentContour;
}
}
所以可能只需要将其更改为在循环
中使用i
而不是0
MatOfPoint currentContour = contours.get(i);
我是 OpenCV4Android 新手。这是我编写的一些代码,用于检测图像中的蓝色斑点。在以下图像中,图像 1 在我的笔记本电脑中。我 运行 应用程序和 OpenCV 相机捕获的帧是图像 2。您可以查看代码以了解其余图像是什么。 (在代码中可以看到,所有图片都保存在SD卡中。)
我有以下问题:。
为什么摄像头拍摄的rgba帧中淡蓝色的斑点颜色变成了淡黄色(如图2)
我在最大的蓝色斑点周围创建了一个
boundingRect
,然后ROI
通过rgbaFrame.submat(detectedBlobRoi)
。但是你可以在最后一张图片中看到,它看起来就像几个灰色像素。 我期待蓝色球体与图像的其余部分分开。
我错过了什么或做错了什么?
代码:
private void detectColoredBlob () {
Highgui.imwrite("/mnt/sdcard/DCIM/rgbaFrame.jpg", rgbaFrame);//check
Mat hsvImage = new Mat();
Imgproc.cvtColor(rgbaFrame, hsvImage, Imgproc.COLOR_RGB2HSV_FULL);
Highgui.imwrite("/mnt/sdcard/DCIM/hsvImage.jpg", hsvImage);//check
Mat maskedImage = new Mat();
Scalar lowerThreshold = new Scalar(170, 0, 0);
Scalar upperThreshold = new Scalar(270, 255, 255);
Core.inRange(hsvImage, lowerThreshold, upperThreshold, maskedImage);
Highgui.imwrite("/mnt/sdcard/DCIM/maskedImage.jpg", maskedImage);//check
Mat dilatedMat= new Mat();
Imgproc.dilate(maskedImage, dilatedMat, new Mat() );
Highgui.imwrite("/mnt/sdcard/DCIM/dilatedMat.jpg", dilatedMat);//check
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(dilatedMat, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//Use only the largest contour. Other contours (any other possible blobs of this color range) will be ignored.
MatOfPoint largestContour = contours.get(0);
double largestContourArea = Imgproc.contourArea(largestContour);
for ( int i=1; i<contours.size(); ++i) {//NB Notice the prefix increment.
MatOfPoint currentContour = contours.get(0);
double currentContourArea = Imgproc.contourArea(currentContour);
if (currentContourArea > largestContourArea) {
largestContourArea = currentContourArea;
largestContour = currentContour;
}
}
Rect detectedBlobRoi = Imgproc.boundingRect(largestContour);
Mat detectedBlobRgba = rgbaFrame.submat(detectedBlobRoi);
Highgui.imwrite("/mnt/sdcard/DCIM/detectedBlobRgba.jpg", detectedBlobRgba);//check
}
- 计算机中的原始图像,这是通过将 phone 的相机放在笔记本电脑屏幕前拍摄的。
- rgbaFrame.jpg
- hsvImage.jpg
- dilatedImage.jpg
- maskedMat.jpg
- detectedBlobRgba.jpg
编辑:
我刚刚使用了 Core.inRange(hsvImage, new Scalar(0,50,40), new Scalar(10,255,255), maskedImage);//3, 217, 225 --- 6, 85.09, 88.24 ...... 3 219 255
,我通过给网站 colorizer.org 一个自定义的红色 HSV 值(即 OpenCV 红色 Scalar(3, 217, 255)
(其中落在给定 inRange
函数中设置的范围内,我将通道值缩放到 colorizer.org 的比例,即 H=0-360,S=0-100,V=0-100,通过将 H 值乘以 2,并将 S 和 V 值都除以 255 再乘以 100。这给了我 6, 85.09, 88.24
我在网站上设置的,并截取了屏幕截图(下图中的第一个)。
- 原始截图,我截取了这一帧。
- rgbaFrame.jpg
- hsvImage.jpg
- maskedImage.jpg
- dilatedMat.jpg
- detectedBlobRgba.jpg
重要提示:
当我触摸 rgbaFrame 内部时,我的测试应用程序实际调用了给定的方法(即它在 onTouch
方法内部调用)。我还使用以下代码将我触摸过的彩色斑点的 Hue
、Saturation
和 Value
值打印到 TextView
。 当我 运行 这个应用程序时,我触摸了红色的斑点,得到了以下值:Hue:3, Saturation:219, Value:255
.
public boolean onTouch(View v, MotionEvent motionEvent) { detectColoredBlob(); int cols = rgbaFrame.cols(); 整数行 = rgbaFrame.rows();
int xOffset = (openCvCameraBridge.getWidth() - cols) / 2;
int yOffset = (openCvCameraBridge.getHeight() - rows) / 2;
int x = (int) motionEvent.getX() - xOffset;
int y = (int) motionEvent.getY() - yOffset;
Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");//check
if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) { return false; }
Rect touchedRect = new Rect();
touchedRect.x = (x > 4) ? x - 4 : 0;
touchedRect.y = (y > 4) ? y - 4 : 0;
touchedRect.width = (x + 4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x;
touchedRect.height = (y + 4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y;
Mat touchedRegionRgba = rgbaFrame.submat(touchedRect);
Mat touchedRegionHsv = new Mat();
Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);
double[] channelsDoubleArray = touchedRegionHsv.get(0, 0);//**********
float[] channelsFloatArrayScaled = new float[3];
for (int i = 0; i < channelsDoubleArray.length; i++) {
if (i == 0) {
channelsFloatArrayScaled[i] = ((float) channelsDoubleArray[i]) * 2;// TODO Wrap an ArrayIndexOutOfBoundsException wrapper
} else if (i == 1 || i == 2) {
channelsFloatArrayScaled[i] = ((float) channelsDoubleArray[i]) / 255;// TODO Wrap an ArrayIndexOutOfBoundsException wrapper
}
}
int androidColor = Color.HSVToColor(channelsFloatArrayScaled);
view.setBackgroundColor(androidColor);
textView.setText("Hue : " + channelsDoubleArray[0] + "\nSaturation : " + channelsDoubleArray[1] + "\nValue : "
+ channelsDoubleArray[2]);
touchedRegionHsv.release();
return false; // don't need subsequent touch events
}
可能您使用的蓝色范围是错误的,在 OpenCV 中,色调范围是 0-180,而您给定的是 170-270。找到正确的蓝色色调值并在 inRange 中使用。
- http://answers.opencv.org/question/30547/need-to-know-the-hsv-value/#30564
- http://answers.opencv.org/question/28899/correct-hsv-inrange-values-for-red-objects/#28901
您可以参考此处的答案以选择正确的 hsv 值。
下面是分割红色的代码,和你的代码核对一下,确保分割的是红色物体。
Imgproc.cvtColor(rgbaFrame, hsv, Imgproc.COLOR_RGB2HSV,4); // Convert to hsv for color segmentation.
Core.inRange(hsv,new Scalar(0,50,40,0), new Scalar(10,255,255,0),thr);//upper red range of hue cylinder
在将图像转换为 HSV 颜色 space 和使用 HSV 颜色 space 时存在多个陷阱。
OpenCV 使用压缩的色调范围,因为原始色调范围从 0 到 360,这意味着值不能适合 1 个字节(值 0 到 255),而饱和度和值通道正好被1个字节覆盖。因此,OpenCV 使用色调值除以 2。因此色调通道将被 0 到 180 之间的矩阵条目覆盖。关于这一点,您的色调范围从 170 到 270 应该除以 2 = OpenCV 中的范围 65 到 135。
色调告诉您色调,但饱和度和明度对于减少噪点仍然很重要,因此也将您的阈值设置为某个最小饱和度和明度
非常重要:OpenCV 使用 BGR 内存排序进行渲染和图像保存。这意味着如果您的图像具有 RGB(a) 排序并且您在没有进行颜色转换的情况下保存它,那么您将交换 R 和 B 通道,因此假设红色将变为蓝色等。不幸的是通常您无法从图像数据本身读取,无论是它是RGB或BGR排序的,所以你应该尝试从图像源中找到它。 OpenCV 允许多个标志从 RGB(A) 转换为 HSV and/or 从 BGR(A) 转换为 HSV,and/or 从 RGB 转换为 BGR 等,所以这没问题,只要你知道哪个图像使用的内存格式。但是,显示和保存始终采用 BGR 顺序,因此如果要显示或保存图像,请将其转换为 BGR!然而,无论您是使用 BGR2HSV 转换 BGR 图像还是使用 RGB2HSV 转换 RGB 图像,HSV 值都是相同的。但是如果你用 RGB2HSV 转换 BGR 图像或用 BGR2HSV 转换 RGB 图像,它会有错误的值......我不是 100% 确定 Java/Python/Android openCV 的 API,但你的图像看起来真的像 B 和 R 通道被交换或误解(但由于您使用 RGBA2HSV 转换,因此 hsv 颜色没有问题)。
关于您的轮廓提取,您的代码中有一个小错误(复制粘贴?),每个人都可能偶尔会观察到:
MatOfPoint largestContour = contours.get(0);
double largestContourArea = Imgproc.contourArea(largestContour);
for ( int i=1; i<contours.size(); ++i) {//NB Notice the prefix increment.
// HERE you had MatOfPoint currentContour = contours.get(0); so you tested the first contour in each iteration
MatOfPoint currentContour = contours.get(i);
double currentContourArea = Imgproc.contourArea(currentContour);
if (currentContourArea > largestContourArea) {
largestContourArea = currentContourArea;
largestContour = currentContour;
}
}
所以可能只需要将其更改为在循环
中使用i
而不是0
MatOfPoint currentContour = contours.get(i);