2017-02-16 15 views
1

Я пытаюсь обнаружить самую большую/большую прямоугольную форму и нарисовать ограничительную рамку для обнаруженной области. В моем случае использования очень часто (и не всегда) объект, представляющий форму прямоугольника, имеет цвет белый, а фон также имеет цвет, очень похожий на белый.OpenCV точить края (края без отверстий)

Прежде чем обнаруживать контуры, я предварительно обработал изображение, чтобы обнаружить идеальный край. Моя проблема заключается в том, что я не могу точно определить края, и у меня есть много шума даже после размытия и использования «адаптивного порога» или «порога».

The original image i have used for contours detection

Я попытался другой способ обнаружить идеальный край в различных условиях освещения, не увенчались успехом.

Как обработать изображение, чтобы обнаружить идеальный край (края без отверстий) для определения контура?

Ниже приведен код, я использую

public static Mat findRectangleX(Mat original) { 
    Mat src = original.clone(); 
    Mat gray = new Mat(); 
    Mat binary = new Mat(); 
    MatOfPoint2f approxCurve; 
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); 

    if (original.type() != CvType.CV_8U) { 
     Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY); 
    } else { 
     original.copyTo(gray); 
    } 

    Imgproc.GaussianBlur(gray, gray, new Size(5,5),0); 
    Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1); 

    //Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU); 


    double maxArea = 0; 
    Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); 

    for (int i = 0; i<contours.size();i++) { 
     MatOfPoint contour = contours.get(i); 
     MatOfPoint2f temp = new MatOfPoint2f(contour.toArray()); 
     double area = Imgproc.contourArea(contour); 
     approxCurve = new MatOfPoint2f(); 
     Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true); 

     if (approxCurve.total() == 4) { 
      Rect rect = Imgproc.boundingRect(contours.get(i)); 
      Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4); 
      if(maxArea < area) 
       maxArea = area; 
     } 
    } 

    Log.v(TAG, "Total contours found : " + contours.size()); 
    Log.v(TAG, "Max area :" + maxArea); 

    return src; 

} 

Я искать подобные проблемы на StackOverflow и попробовать образец кода, но ни один из них работал для меня. Трудность, я думаю, это белый объект на белом фоне.

Как обработать изображение, чтобы обострить края для определения контура?

Как определить максимальную или большую прямоугольную форму и нарисовать прямоугольную линию для обнаруженной формы?

// Обновлено: 20/02/2017

я попробовал решение, предложенное @Nejc в следующей почте. Сегментация лучше, но у меня все еще есть отверстия в контуре, и findcontours не удается обнаружить более крупный контур. Ниже приведен код, предоставленный @Nejc и переведенный в java.

public static Mat process(Mat original){ 
    Mat src = original.clone(); 
    Mat hsvMat = new Mat(); 
    Mat saturation = new Mat(); 
    Mat sobx = new Mat(); 
    Mat soby = new Mat(); 
    Mat grad_abs_val_approx = new Mat(); 

    Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV); 
    List<Mat> hsv_channels = new ArrayList<Mat>(3); 
    Core.split(hsvMat, hsv_channels); 
    Mat hue = hsv_channels.get(0); 
    Mat sat = hsv_channels.get(1); 
    Mat val = hsv_channels.get(2); 

    Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2); 
    Mat imf = new Mat(); 
    saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f); 

    Imgproc.Sobel(imf, sobx, -1, 1, 0); 
    Imgproc.Sobel(imf, soby, -1, 0, 1); 

    sobx = sobx.mul(sobx); 
    soby = soby.mul(soby); 

    Mat abs_x = new Mat(); 
    Core.convertScaleAbs(sobx,abs_x); 
    Mat abs_y = new Mat(); 
    Core.convertScaleAbs(soby,abs_y); 
    Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx); 

    sobx.release(); 
    soby.release(); 


    Mat filtered = new Mat(); 
    Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2); 

    final MatOfDouble mean = new MatOfDouble(); 
    final MatOfDouble stdev = new MatOfDouble(); 
    Core.meanStdDev(filtered, mean, stdev); 

    Mat thresholded = new Mat(); 
    Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO); 

    /* 
    Mat thresholded_bin = new Mat(); 
    Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY); 
    Mat converted = new Mat(); 
    thresholded_bin.convertTo(converted, CV_8UC1); 
    */ 

    return thresholded; 
} 

Вот изображение, которое я получил после выполнения кода выше

Image after using @Nejc solution

1) Почему мой транслируемый код не выводит то же изображение, как @Nejc? Тот же код, примененный к тому же изображению, должен выдавать тот же результат?

2) я что-то пропустил при переводе?

3) Для моего понимания, почему мы умножили изображение самостоятельно в этой инструкции sobx = sobx.mul (sobx); ?

+0

Что вы подразумеваете под "идеальным краем"? Совершенно прямо? – Rethunk

+0

затачивайте края изображения, чтобы лучше определить контуры. –

ответ

0

Мне удалось получить довольно приятное изображение края, вычислив приближение абсолютного значения градиента входного изображения.

EDIT: Прежде чем я начал работать, я изменил размер входного изображения на 5x меньшего размера. Click here to see it!. Если вы используете мой код на этом изображении, результаты будут хорошими.Если вы хотите, чтобы мой код работает хорошо с изображением оригинального размера, затем:

  • умножают гауссовских размеры ядра и сигмы по 5 или
  • декодируют изображение на коэффициент 5, выполнения алгоритма и то повышающую дискретизацию результата за счет фактора 5 (это должно работать гораздо быстрее, чем первый вариант)

Это результат я получил:

enter image description here

enter image description here

Моя процедура основана на двух ключевых характеристиках. Первый - это преобразование в соответствующее цветовое пространство. As Jeru Luke stated in his answer, канал насыщения в цветовом пространстве HSV является хорошим выбором здесь. Вторая важная вещь - оценка абсолютной величины градиента. Для этой цели я использовал операторы sobel и некоторую арифметику. Я могу предоставить дополнительные пояснения, если кто-то их попросит.

Это код, который я использовал для получения первого изображения.

using namespace std; 
using namespace cv; 

Mat img_rgb = imread("letter.jpg"); 

Mat img_hsv; 
cvtColor(img_rgb, img_hsv, CV_BGR2HSV); 
vector<Mat> channels_hsv; 
split(img_hsv, channels_hsv); 

Mat channel_s = channels_hsv[1]; 
GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2); 

Mat imf; 
channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f); 

Mat sobx, soby; 
Sobel(imf, sobx, -1, 1, 0); 
Sobel(imf, soby, -1, 0, 1); 

sobx = sobx.mul(sobx); 
soby = soby.mul(soby); 

Mat grad_abs_val_approx; 
cv::pow(sobx + soby, 0.5, grad_abs_val_approx); 

Mat filtered; 
GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2); 

Scalar mean, stdev; 
meanStdDev(filtered, mean, stdev); 

Mat thresholded; 
cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO); 

// I scale the image at this point so that it is displayed properly 
imshow("image", thresholded/50); 

И это, как я вычислил второе изображение:

Mat thresholded_bin; 
cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY); 

Mat converted; 
thresholded_bin.convertTo(converted, CV_8UC1); 

vector<vector<Point>> contours; 
findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); 

Mat contour_img = Mat::zeros(converted.size(), CV_8UC1); 
drawContours(contour_img, contours, -1, 255); 

imshow("contours", contour_img); 
+0

Благодарим за ваше решение. Это то, что я пытаюсь иметь. Я добавил комментарий выше. –

+0

@ctbcorp О вопросе 1): Я добавил раздел «Изменить» в начало сообщения. О вопросе 2) Я заметил две ошибки: вы передаете 'grad_abs_val_approx' вместо' filter' в качестве аргумента при вызове функций 'meanStdDev' и' threshold'. О вопросе 3): Когда я умножил два массива самостоятельно и вычислил квадратный корень из их суммы, я сделал это: sqrt (a² + b²). Если 'a' и' b' являются приближениями к градиенту в направлении x и y, результатом является величина градиента. – Nejc

+0

Спасибо за эту информацию. Я только что просмотрел ваш комментарий после обновления моего сообщения. я попробую еще раз с новой информацией, которую вы предоставили. –

0

Спасибо за Ваши комментарии и предложения. Код, предоставленный @NEJC, отлично работает и охватывает 80% моего варианта использования.

Тем не менее, он не работает с похожим случаем case not solved by the current code и я не знаю почему.

Возможно, у кого-то есть идея/ключ/решение?

Я продолжаю совершенствовать код и пытаюсь найти более общее решение, которое может охватывать больше случаев. Я отправлю его, если я когда-нибудь найду.

В любом случае ниже приведен рабочий код на основе решения и примечаний @NEJC.

public static Mat process(Mat original){ 
    Mat src = original.clone(); 
    Mat hsvMat = new Mat(); 
    Mat saturation = new Mat(); 
    Mat sobx = new Mat(); 
    Mat soby = new Mat(); 
    Mat grad_abs_val_approx = new Mat(); 

    Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV); 
    List<Mat> hsv_channels = new ArrayList<Mat>(3); 
    Core.split(hsvMat, hsv_channels); 
    Mat hue = hsv_channels.get(0); 
    Mat sat = hsv_channels.get(1); 
    Mat val = hsv_channels.get(2); 

    Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2); 
    Mat imf = new Mat(); 
    saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f); 

    Imgproc.Sobel(imf, sobx, -1, 1, 0); 
    Imgproc.Sobel(imf, soby, -1, 0, 1); 

    sobx = sobx.mul(sobx); 
    soby = soby.mul(soby); 

    Mat sumxy = new Mat(); 
    Core.add(sobx,soby, sumxy); 
    Core.pow(sumxy, 0.5, grad_abs_val_approx); 

    sobx.release(); 
    soby.release(); 
    sumxy.release();; 


    Mat filtered = new Mat(); 
    Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2); 

    final MatOfDouble mean = new MatOfDouble(); 
    final MatOfDouble stdev = new MatOfDouble(); 
    Core.meanStdDev(filtered, mean, stdev); 

    Mat thresholded = new Mat(); 
    Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO); 


    /* 
    Mat thresholded_bin = new Mat(); 
    Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV); 
    Mat converted = new Mat(); 
    thresholded_bin.convertTo(converted, CV_8UC1); 
    */ 

    Mat converted = new Mat(); 
    thresholded.convertTo(converted, CV_8UC1); 
    return converted; 
} 
+0

Канал насыщения не является хорошим выбором в этом случае. У меня получились хорошие результаты, когда я использовал изображение ценности или преобразованное в оттенки серого изображение вместо этого + мне также пришлось понизить порог (только среднее значение вместо среднего + stdev). Вам, вероятно, понадобится какой-то алгоритм, который автоматически определит, какой канал изображения использовать, прежде чем запускать этот код ... – Nejc