Wednesday, March 26, 2014

Edge Detection with 1D Inplace Fast Haar Wavelet Transform Applied to Image Columns


Problem Statement

In a previous post, we used the 1D inplace fast Haar wavelet transform to design a simple technique to detect edges in images by applying the transform to image rows. In this post, we will detect edges by applying the same transform to image columns. We will again use OpenCV to do the coding.  The implementation of the 1D inplace fast Haar wavelet transform remains unchanged (source code is here; look for the function inPlaceFastHaarWaveletTransform).

cv::Mat Manipulation

We need to extract columns from cv::Mat structures. Unlike in this post where we applied the Haar transform to image rows, in this post we apply the transform to columns. Edges are detected by looking at the absolute magnitudes of Haar wavelets at specific frequencies (e.g., 1/2^{n-1}, where n is the length of the image column which has to be an integral power of 2 for the transform to work). We will again use grayscale images that can be constructed in OpenCV as follows: 

cv::Mat mat(n, n, CV_8U, cv::Scalar(0));

Grayscale cv::Mats consist of uchar rows and columns.  Hence, a simple function to convert a column of uchars into an array of doubles.

double* ucharColAryToDoubleAry(cv::Mat &img, int colnum, int num_rows) {
  double *colAry = new double[num_rows];
  for(int r = 0; r < num_rows; r++) {
    colAry[r] = (double)img.at<uchar>(r, colnum);
  }
  return colAry;
}



Edge Detection

The function haarDetectVerEdges, given below, uses the highest frequency Haar wavelets computed in the current image column to detect edges. The function goes through the image specified in the first argument column by column. It takes the wavelet bandwidth defined by the next two arguments: lower_theta and upper_theta. The fourth parameter, rv, specifies the value on the scale [0, 255] that we want a pixel to contain if that pixel contains an edge. If the value of the wavelet is negative, then the current pixel contains an edge. If the value of the wavelet is positive, the above pixel contains an edge.

void haarDetectVerEdges(cv::Mat &img, int lower_theta, int upper_theta, int rv)
{
  const int nr = img.rows;
  const int nc = img.cols * img.channels();
  double* converted_col_data;
  double hd = 0.0;

  for(int c = 0; c < nc; c++) {
    converted_col_data = ucharColAryToDoubleAry(img, c, nr);
    inPlaceFastHaarWaveletTransform(converted_col_data, nr);
    for(int r = 0; r < nr; r += 2) {
      hd = converted_col_data[r];    
      if ( std::abs(hd) > lower_theta && std::abs(hd) < upper_theta ) {
    if ( hd < 0 ) {
      img.at<uchar>(r, c) = (uchar)rv;
    }
    else if ( hd > 0 && c > 0 ) {
      img.at<uchar>(r-1, c) = (uchar)rv;
    }
      }
    }
    delete [] converted_col_data;
  }
}


Tests

Let us write a few tools tests. The functions createGrayscaleSquareMat(), drawCircle(), etc. are given in this post. The first test, testColHaar01(), creates a grayscale 256 x 256 image and draws several ellipses on it. The created image is shown in Figure 1 and is displayed with cv::imshow()

void testColHaar01() {
  cv::Mat img = cv::Mat::zeros(256, 256, CV_8U);
  drawEllipse(img, 90);
  drawEllipse(img, 0);
  drawEllipse(img, 45);
  drawEllipse(img, -45);
  cv::imshow("col orig", img);
  haarDetectVerEdges(img, 125, 128, 255);
  cv::imshow("col edge", img);
  cv::waitKey(0);
}



Figure 1. Image with ellipses
When the function haarDetecVerEdges is applied to the image in Figure 1, the resulting image is given in Figure 2.


Figure 2. Edges detected in Figure 1



The second test, testColHaar02() , draws one shallow and one filled rectangle, as shown in Figure 3, and then applies the 1D inplace fast Haar transform to its columns. The result is shown in Figure 4.



 void testColHaar02() {
  cv::Mat img = cv::Mat::zeros(256, 256, CV_8U);
  cv::rectangle(img, cv::Point(10, 10), cv::Point(40, 40), cv::Scalar(255), 1);
  cv::rectangle(img, cv::Point(40, 40), cv::Point(70, 70), cv::Scalar(255), -1);
  cv::imshow("col orig", img);
  haarDetectHorEdges(img, 125, 128, 255);
  cv::imshow("col edge", img);
  cv::waitKey(0);
}
Figure 3. Image with two squares



Figure 4. Edges detected in Figure3