Попытка определить синий цвет изображения с помощью opencv и получение неожиданного результата

Я новичок в 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
     }
  1. Исходное изображение на компьютере. Это было снято путем размещения камеры телефона перед экраном ноутбука.

введите здесь описание изображения

  1. rgbaFrame.jpg

введите здесь описание изображения

  1. hsvImage.jpg

введите здесь описание изображения

  1. растянутое изображение.jpg

введите здесь описание изображения

  1. Маскированный мат.jpg

введите здесь описание изображения

  1. обнаруженBlobRgba.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, который я установил на веб-сайте, и сделал скриншот (первый на следующих изображениях).

  1. Оригинальный скриншот, я заснял этот кадр.

введите здесь описание изображения

  1. rgbaFrame.jpg

введите здесь описание изображения

  1. hsvImage.jpg

введите здесь описание изображения

  1. МаскированныйИзображение.jpg

введите здесь описание изображения

  1. растянутый мат.jpg

введите здесь описание изображения

  1. обнаруженBlobRgba.jpg

введите здесь описание изображения


ВАЖНО:

Данный метод фактически вызывается в моем тестовом приложении, когда я касаюсь внутри rgbaFrame (т.е. он вызывается внутри метода onTouch). Я также использую следующий код для печати в TextView значений Hue, Saturation и Value цветного пятна, к которому я прикоснулся. Запустив это приложение, я коснулся красной точки и получил следующие значения: 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 

}


person Solace    schedule 09.12.2015    source источник
comment
opencv делит все значения оттенка на 2, чтобы уместить 360 градусов в 1 байт. Таким образом, весь диапазон оттенков находится в пределах 0..180 в openCV. вы также должны использовать некоторые минимальные значения каналов s и v, чтобы уменьшить влияние шума, но оттенок будет самым важным.   -  person Micka    schedule 09.12.2015
comment
@Мика Спасибо. Я попробовал диапазон, предложенный Харисом в их ответе, который также имел диапазон для каналов s и v, и это значительно уменьшило шум (как вы можете видеть в редактировании). Но увы, проблемы остались: (1) Почему красный цвет в исходном изображении преобразуется в синеватый в rgbaFrame.jpg. (2) Почему субмат, созданный из области интереса и хранящийся в файле detectBlobRgba.jpg, такой маленький? Разве это не должно быть большое красное пятно?   -  person Solace    schedule 09.12.2015
comment
не проверял ваш код, но если изображение действительно RGBa, оно будет отображаться неправильно, потому что openCV использует порядок BGRa для отображения и сохранения (не уверен в API java/android/python). Вы можете использовать функцию cvtColor для преобразования из rgb в bgr. Используйте соответственно флаги BGR2HSV или RGB2HSV.   -  person Micka    schedule 09.12.2015
comment
about (2): разве самая большая часть замаскированной области в обнаруженномBlobRgba.jpg не является красным пятном исходного изображения?!?   -  person Micka    schedule 09.12.2015
comment
поэтому, если мой предыдущий комментарий не был ясен: о (1): преобразование из RGBA в BGR перед сохранением или отображением изображения, вероятно, решит проблему rgbaFrame.jpg!   -  person Micka    schedule 09.12.2015
comment
@Micka О (1), я попытался изменить флаг с Imgproc.COLOR_RGB2HSV_FULL на Imgproc.Imgproc.COLOR_BGR2HSV_FULL, но это ничего не изменило (красный цвет на исходном изображении по-прежнему выглядит голубоватым в rgbaFrame). Затем я подумал, что, возможно, мне придется использовать cvtColor для преобразования rgbaFrame в формат BGR, когда он создается в функции onCameraFrame с помощью rgbaFrame = inputFrame.rgba(), но здесь в документации не упоминается преобразование из RGB в BGR.   -  person Solace    schedule 09.12.2015
comment
@Micka О вашем ответе на (2), Да! Но я думаю, вы думаете, что подписи в моем вопросе принадлежат изображениям над ними. На самом деле все наоборот. Изображение, обнаруженное в вопросеBlobRgba.jpg, находится под меткой/заголовком '6. обнаруженBlobRgba'. Это изображение — жирная черноватая точка под заголовком. Это то, что мало (и сомнительно).   -  person Solace    schedule 09.12.2015
comment
пожалуйста, попробуйте в любом случае, так как вы можете видеть, что красная часть теперь выглядит синей, что явно связано с путаницей bgr и rgb.   -  person Micka    schedule 09.12.2015
comment
о (2): на входном изображении вы видите большой синий квадрат, который должен быть красным. но поскольку ваш hsv был (вероятно) правильно рассчитан из rgb2hsv, этот квадрат должен иметь правильные значения оттенка красного цвета. поскольку вы использовали значение inRange выше 170 (что составляет 340 в реальном диапазоне оттенков), вы запрашиваете красный цвет около 360 градусов, и это может быть этот квадрат (сам не мог проверить)   -  person Micka    schedule 09.12.2015
comment
насчет маленького изображения блоба: не проверял ваш код, это проблема приоритета? возможно, ошибка в вашем извлечении/интерпретации контура   -  person Micka    schedule 09.12.2015
comment
контурная петля имеет ошибку, вы всегда берете индекс 0 вместо индекса i   -  person Micka    schedule 09.12.2015
comment
@Micka Если я каким-то образом найду способ преобразовать rgbaFrame в BGR, получу ли я (только) красный квадрат в обнаруженномBlobRgba.jpg? (Это мой первый раз, когда я использую OpenCV, поэтому я не уверен, как все работает).   -  person Solace    schedule 09.12.2015
comment
@Micka Ооо, мой плохой! спасибо, что указали на это, синий квадрат захвачен в обнаруженномBlobRgb.jpg. Прости за глупость.   -  person Solace    schedule 09.12.2015
comment
@Micka Можете ли вы написать суть ваших комментариев в качестве ответа, чтобы я мог пометить его как принятый ответ?   -  person Solace    schedule 09.12.2015


Ответы (2)


Существует несколько ловушек при преобразовании изображения в цветовое пространство HSV и использовании цветового пространства HSV.

  1. OpenCV использует сжатый диапазон оттенков, потому что исходный оттенок находится в диапазоне от 0 до 360, что означает, что значения не могут уместиться в 1 байт (значения от 0 до 255), в то время как каналы насыщенности и значения точно покрываются 1 байтом. Поэтому OpenCV использует значения оттенка, разделенные на 2. Таким образом, канал оттенка будет покрыт элементами матрицы от 0 до 180. В связи с этим ваш диапазон оттенков от 170 до 270 должен быть разделен на 2 = диапазон от 65 до 135 в OpenCV.

  2. оттенок говорит вам о цветовом тоне, но насыщенность и значение по-прежнему важны для уменьшения шума, поэтому также установите порог на минимальную насыщенность и значение

  3. очень важно: OpenCV использует порядок памяти BGR для рендеринга и сохранения изображений. Это означает, что если ваше изображение имеет порядок RGB (a) и вы сохраняете его без преобразования цвета, вы меняете местами каналы R и B, поэтому предполагаемый красный цвет станет синим и т. д. К сожалению, обычно вы не можете читать из самих данных изображения, будь то это упорядоченный RGB или BGR, поэтому вы должны попытаться выяснить это из источника изображения. OpenCV позволяет преобразовывать несколько флагов из RGB(A) в HSV и/или из BGR(A) в HSV, и/или из RGB в BGR и т. д., так что это не проблема, если вы знаете, в каком формате памяти находится ваше изображение. использует. Однако отображение и сохранение всегда предполагает порядок BGR, поэтому, если вы хотите отобразить или сохранить изображение, преобразуйте его в BGR! Однако значения HSV будут одинаковыми, независимо от того, конвертируете ли вы изображение BGR с помощью BGR2HSV или преобразуете ли вы изображение RGB с помощью RGB2HSV. Но у него будут неправильные значения, если вы конвертируете изображение BGR с RGB2HSV или изображение RGB с BGR2HSV... Я не уверен на 100% в API Java/Python/Android для openCV, но ваше изображение действительно выглядит как каналы 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);
person Micka    schedule 09.12.2015

Вероятно, диапазон, который вы используете, неверен для синего. В OpenCV диапазон оттенков составляет от 0 до 180, а вы указали его от 170 до 270. Найдите правильное значение оттенка для синего и используйте его в inRange.

  1. http://answers.opencv.org/question/30547/need-to-know-the-hsv-value/#30564
  2. 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 
person Haris    schedule 09.12.2015
comment
диапазон оттенков красного также должен иметь некоторую часть в конце диапазона оттенков (около значения 180 в openCV). Я бы попробовал оттенки openCV 110-125 для синего цвета в качестве начального диапазона. - person Micka; 09.12.2015
comment
Что такое 4-е значение в скалярах. HSV имеет только три канала - person Solace; 09.12.2015
comment
Я только что проверил ваш диапазон на красный, отредактировал вопрос, чтобы опубликовать результат. Остаются вопросы (хотя шум на изображении, кажется, значительно уменьшился): (1) Почему красный цвет в исходном изображении превращается в голубоватый в rgbaFrame.jpg. (2) Почему submat, созданный из ROI и хранящийся в detectedBlobRgba.jpg, такой маленький? Разве это не должно быть большое красное пятно? - person Solace; 09.12.2015
comment
@Micka Да, но я попытался сделать снимок экрана с веб-сайта colorizer.org и задал собственное значение для HSV, которое попадает в диапазон, переданный в inRange. Вы можете увидеть первый абзац моего редактирования в вопросе. - person Solace; 09.12.2015