C ++ / OpenCV中的本地阈值实现

时间:2015-01-14 17:03:43

标签: c++ opencv

我想实现一个本地阈值算法,我需要你的专业知识。

我的图片大小调整为600x400,灰度。

本地化的基本思维过程:

  • 使用每个像素拍摄的9x9 ROI对图像进行分割,并计算该区域的最大强度。
  • 创建一个9x9内核。

    条件:

    • 如果蒙版的中心像素高于最大强度的50%,则设置中心像素为真。(应用蒙版)

我的问题是:

  • 我应该如何选择我的内核/面具?

      cv::Mat ROI;
      cv::Mat mask(input.size(),CV_8UC1, cv::Scalar::all(0)); // create mask of 0s at first
      const int kerneldepth = 1;
      const int kernelsize = 9;
      cv::Mat kernel = cv::Mat::ones( kernelsize, kernelsize, CV_8UC1 );
    
    
    //take ROI of 9x9 and apply a threshold
    
    for( double x = 9; x < input.cols -9; x++ ){
         for( double y = 9 ; y < input.rows - 9 ; y++ ){
    
            try{
              double x_left = x - 4;
              double x_right = x + 4;
              double y_up = y + 4;
              double y_down = y - 4;
              double maxVal;
              double minVal;
              cv::Point anchor(kernelsize/2,kernelsize/2);
    
              cv::Rect ROI = cv::Rect(x_left,y_down,9,9);
              cv::Mat ROI_Mat = input(ROI);                                // a new matrix for ROI
              cv::Scalar avgPixelIntensity = cv::mean( ROI_Mat );          // calculate mean
              cv::minMaxLoc(ROI_Mat,&minVal,&maxVal);
    
              if( input.at<uchar>(x,y) >= 0.5*maxVal){
    
              cv::filter2D(input,mask,-1,kernel,anchor,0);
        } else { break;}
    
        }
    
        catch (cv::Exception &e){
    
            e.what();
        }
    
    
       }
    
      *****************************UPDATED CODE: ******************************************
    
        applyLocalThresh(cv::Mat &src, cv::Mat& out){
          double maxVal, minVal;
          cv::Mat output;
          int top, bottom, left , right;
          int borderType = cv::BORDER_CONSTANT;
          cv::Scalar value;
          top = (int) (9); bottom = (int) (9);
          left = (int) (9); right = (int) (9);
          output = src;
          out = src;
          value = 0;
          cv::copyMakeBorder(src,output,top,bottom,left,right,borderType,value);
    
         for(int y = 9; y < src.rows; y++) {
    
            for(int x = 9; x < src.cols; x ++) {
    
                    cv::Mat ROI = src(cv::Rect(cv::Point(x-4,y-4),cv::Size(9,9)));
                    cv::minMaxLoc(ROI,&minVal,&maxVal);
    
            if(src.at<uchar>(cv::Point(x-4,y-4)) >= 0.6*maxVal){
    
            out.at<uchar>(cv::Point(x-4,y-4)) = 255;
        }else{
            out.at<uchar>(cv::Point(x-4,y-4));
    
            }
        }
    }
     }
    

3 个答案:

答案 0 :(得分:3)

你可以通过扩张然后在OpenCV中进行比较来做到这一点;

im = load image here;
di = dilate im with a 9x9 kernel;
bw = im > (di * 0.5); // in OpenCV, pixels of bw are set to 255 or 0

一个简单的例子用Matlab / Octave中的4x6图像和3x3内核来说明这一点:

im =

 1     2     3     4     5     6
 2     3     4     5     6     7
 3     4     5     6     7     8
 4     5     6     7     8     9

di =

 3     4     5     6     7     7
 4     5     6     7     8     8
 5     6     7     8     9     9
 5     6     7     8     9     9

th = di * .5

th =

1.5000    2.0000    2.5000    3.0000    3.5000    3.5000
2.0000    2.5000    3.0000    3.5000    4.0000    4.0000
2.5000    3.0000    3.5000    4.0000    4.5000    4.5000
2.5000    3.0000    3.5000    4.0000    4.5000    4.5000

bw = im&gt;个

bw =

 0     0     1     1     1     1
 0     1     1     1     1     1
 1     1     1     1     1     1
 1     1     1     1     1     1

答案 1 :(得分:2)

我担心这种做法并不完全正确。让我解释一下:对于涉及内核的操作,必须小心将内核的中心放在要转换的像素之上。这是因为3x3,5x5,7x7,9x9(...)内核只计算图像中一个像素的值,这是位于中心的那个[0,0]内核。

如果考虑如何计算图像第一个像素的值,9x9内核的中心将放置在坐标 [0,0] 处。这意味着3/4的内核将被放置在负坐标处,即坐标指的是不存在的像素:

 [-4,-4][-3,-4][-2,-4][-1,-4][ 0,-4][ 1,-4][ 2,-4][ 3,-4][ 4,-4]
 [-4,-3][-3,-3][-2,-3][-1,-3][ 0,-3][ 1,-3][ 2,-3][ 3,-3][ 4,-3]
 [-4,-2][-3,-2][-2,-2][-1,-2][ 0,-2][ 1,-2][ 2,-2][ 3,-2][ 4,-2]
 [-4,-1][-3,-1][-2,-1][-1,-1][ 0,-1][ 1,-1][ 2,-1][ 3,-1][ 4,-1]
 [-4, 0][-3, 0][-2, 0][-1, 0][ 0, 0][ 1, 0][ 2, 0][ 3, 0][ 4, 0]
 [-4, 1][-3, 1][-2, 1][-1, 1][ 0, 1][ 1, 1][ 2, 1][ 3, 1][ 4, 1]
 [-4, 2][-3, 2][-2, 2][-1, 2][ 0, 2][ 1, 2][ 2, 2][ 3, 2][ 4, 2]
 [-4, 3][-3, 3][-2, 3][-1, 3][ 0, 3][ 1, 3][ 2, 3][ 3, 3][ 4, 3]
 [-4, 4][-3, 4][-2, 4][-1, 4][ 0, 4][ 1, 4][ 2, 4][ 3, 4][ 4, 4]

对于图像边界附近的像素,总会发生这种情况。因此,对于第一个像素的计算,我们必须将计算限制在内核的1/4,指目标图像中的有效坐标:

 [     ][     ][     ][     ][     ][     ][     ][     ][     ]
 [     ][     ][     ][     ][     ][     ][     ][     ][     ]
 [     ][     ][     ][     ][     ][     ][     ][     ][     ]
 [     ][     ][     ][     ][     ][     ][     ][     ][     ]
 [     ][     ][     ][     ][ 0, 0][ 1, 0][ 2, 0][ 3, 0][ 4, 0]
 [     ][     ][     ][     ][ 0, 1][ 1, 1][ 2, 1][ 3, 1][ 4, 1]
 [     ][     ][     ][     ][ 0, 2][ 1, 2][ 2, 2][ 3, 2][ 4, 2]
 [     ][     ][     ][     ][ 0, 3][ 1, 3][ 2, 3][ 3, 3][ 4, 3]
 [     ][     ][     ][     ][ 0, 4][ 1, 4][ 2, 4][ 3, 4][ 4, 4]

因此,您当前的方法存在的问题是,在某些时候您将设置一个具有负坐标的ROI,并且当执行这些指令时,您将看到一个不错的崩溃:

cv::Mat ROI_Mat = input(ROI);  // crash 

解决方案不是使用ROI而是自己实现该算法。我无法看到此自定义计算与cv::filter2D()一起使用。这里有一些东西可以帮助您入门:

void local_threshold(const cv::Mat& input, cv::Mat& output)
{
    if (input.channels() != 1)
    {
        std::cout << "local_threshold !!! input image must be single channel" << std::endl;
        return;
    }

    output = cv::Mat(input.rows, input.cols, CV_8UC1);
    double min_val = 0, max_val = 0;

    for (int i = 0; i < input.rows; i++)
        for (int j = 0; j < input.cols; j++)
        {
            cv::Mat kernel = Mat::zeros(9, 9, output.type());

            // Implement logic to fill the 9x9 kernel with
            // values from the input Mat, respecting boundaries.

            cv::Scalar avg_intensity = cv::mean(kernel);
            cv::minMaxLoc(kernel, &min_val,&max_val);

            if (input.at<uchar>(i,j) > (max_val / 2))
                output.at<unsigned char>(i,j) = 255;
            else
                output.at<unsigned char>(i,j) = 0;
        }
}

答案 2 :(得分:2)

在进一步思考并找到如何利用我在编程方面的基本知识后,我想出了这个代码并不是最有效但完成工作的代码。

我的方法的主要问题是什么? :

  • 边界像素,其中一个主要问题和内核与掩码之间的整个索引操作引起了轻微的头痛。

我解决这个问题的方法是什么?:

  • 我的阈值表示需要相对较高的强度级别来设置真实像素。因此,我用一些假想的负像素填充图像,并使我的算法从原始的第一个像素开始。并将结果保存到面具中。

结果:

  • SUCCESS!

代码:

     double maxVal, minVal;
     cv::Mat output;
     int top, bottom, left , right;
     int borderType = cv::BORDER_CONSTANT;
     cv::Scalar value;
     top = (int) (4); bottom = (int) (4);
     left = (int) (4); right = (int) (4);
     output = src;
     out = src;
     value = 0;
     cv::copyMakeBorder(src,output,top,bottom,left,right,borderType,value);

for(int y = 4; y < output.rows - 4; y++) {

    for(int x = 4; x < output.cols - 4; x ++) {

        // apply local ROI

        cv::Mat ROI = output(cv::Rect(cv::Point(x-4,y-4),cv::Size(9,9)));
        cv::minMaxLoc(ROI,&minVal,&maxVal);    // extract max intensity values in the ROI

        if(src.at<uchar>(cv::Point(x-4,y-4)) >= 0.5*maxVal){    // apply local threshold w.r.t highest intensity level

            out.at<uchar>(cv::Point(x-4,y-4)) = 255;            // change pixel value in mask if true
        }else{
            out.at<uchar>(cv::Point(x-4,y-4)) = 0;

            }

        }
    }



}

它需要一些清理我知道,但希望这会帮助其他人得到一些想法。