canny.h

Go to the documentation of this file.
00001 
00015 #ifndef _DLRCOMPUTERVISION_CANNY_H_
00016 #define _DLRCOMPUTERVISION_CANNY_H_
00017 
00018 #include <dlrComputerVision/image.h>
00019 
00020 namespace dlr {
00021 
00022   namespace computerVision {
00023     
00024 
00067     template <ImageFormat FORMAT>
00068     Image<GRAY1>
00069     applyCanny(const Image<FORMAT>& inputImage,
00070                size_t gaussianSize = 5,
00071                double upperThreshold = 0.0,
00072                double lowerThreshold = 0.0,
00073                double autoUpperThresholdFactor = 3.0,
00074                double autoLowerThresholdFactor = 0.0);
00075 
00076   } // namespace computerVision
00077 
00078 } // namespace dlr
00079 
00080 
00081 /* =============== Implementation follows =============== */
00082 
00083 #include <limits>
00084 #include <list>
00085 #include <dlrComputerVision/filter.h>
00086 #include <dlrComputerVision/kernels.h>
00087 #include <dlrComputerVision/nonMaximumSuppress.h>
00088 #include <dlrComputerVision/sobel.h>
00089 #include <dlrComputerVision/utilities.h>
00090 #include <dlrNumeric/index2D.h>
00091 #include <dlrNumeric/utilities.h>
00092 
00093 namespace dlr {
00094 
00095   namespace computerVision {
00096 
00098     namespace privateCode {
00099 
00100       Image<GRAY1>
00101       traceEdges(const Image<GRAY_FLOAT64>& gradImage, double threshold)
00102       {
00103         Image<GRAY1> edgeImage(gradImage.rows(), gradImage.columns());
00104         edgeImage = false;
00105 
00106         // Make a pass through the image pushing all the seed points
00107         // onto our list of edges to trace.
00108         std::list<Index2D> seedList;
00109         size_t index0 = 0;
00110         for(size_t row = 0; row < gradImage.rows(); ++row) {
00111           for(size_t column = 0; column < gradImage.columns(); ++column) {
00112             if(gradImage[index0] > threshold) {
00113               edgeImage[index0] = true;
00114               seedList.push_front(Index2D(static_cast<int>(row),
00115                                           static_cast<int>(column)));
00116             }
00117             ++index0;
00118           }
00119         }
00120 
00121         // Work our way through all edges that must be traced.
00122         size_t lastRow = gradImage.rows() - 1;
00123         size_t lastColumn = gradImage.columns() - 1;
00124         size_t columns = gradImage.columns();
00125         while(seedList.size() != 0) {
00126 
00127           // Get the next edge to trace.
00128           Index2D seed = *seedList.begin();
00129           seedList.pop_front();
00130 
00131           // Don't trace edges on the very outside border of the image.
00132           size_t row = seed.getRow();
00133           size_t column = seed.getColumn();
00134           if(row == 0 || column == 0
00135              || row == lastRow || column == lastColumn) {
00136             continue;
00137           }
00138 
00139           // Use single indexing on the assumption that it's faster.
00140           index0 = row * columns + column;
00141 
00142           // Inspect each neighbor in turn, marking as appropriate,
00143           // and pushing any newly marked neighbor pixels onto the
00144           // list so that they will themselves be traced.
00145           size_t neighborIndex = index0 - columns - 1;
00146           if(gradImage(neighborIndex) != 0.0
00147              && edgeImage(neighborIndex) == false) {
00148             edgeImage(neighborIndex) = true;
00149             seedList.push_front(Index2D(static_cast<int>(row) - 1,
00150                                         static_cast<int>(column) - 1));
00151           }
00152           ++neighborIndex;
00153           if(gradImage(neighborIndex) != 0.0
00154              && edgeImage(neighborIndex) == false) {
00155             edgeImage(neighborIndex) = true;
00156             seedList.push_front(Index2D(static_cast<int>(row) - 1,
00157                                         static_cast<int>(column)));
00158           }
00159           ++neighborIndex;
00160           if(gradImage(neighborIndex) != 0.0
00161              && edgeImage(neighborIndex) == false) {
00162             edgeImage(neighborIndex) = true;
00163             seedList.push_front(Index2D(static_cast<int>(row) - 1,
00164                                         static_cast<int>(column) + 1));
00165           }
00166           neighborIndex = index0 - 1;
00167           if(gradImage(neighborIndex) != 0.0
00168              && edgeImage(neighborIndex) == false) {
00169             edgeImage(neighborIndex) = true;
00170             seedList.push_front(Index2D(static_cast<int>(row),
00171                                         static_cast<int>(column) - 1));
00172           }
00173           neighborIndex = index0 + 1;
00174           if(gradImage(neighborIndex) != 0.0
00175              && edgeImage(neighborIndex) == false) {
00176             edgeImage(neighborIndex) = true;
00177             seedList.push_front(Index2D(static_cast<int>(row),
00178                                         static_cast<int>(column) + 1));
00179           }
00180           neighborIndex = index0 + columns - 1;
00181           if(gradImage(neighborIndex) != 0.0
00182              && edgeImage(neighborIndex) == false) {
00183             edgeImage(neighborIndex) = true;
00184             seedList.push_front(Index2D(static_cast<int>(row) + 1,
00185                                         static_cast<int>(column) - 1));
00186           }
00187           ++neighborIndex;
00188           if(gradImage(neighborIndex) != 0.0
00189              && edgeImage(neighborIndex) == false) {
00190             edgeImage(neighborIndex) = true;
00191             seedList.push_front(Index2D(static_cast<int>(row) + 1,
00192                                         static_cast<int>(column)));
00193           }
00194           ++neighborIndex;
00195           if(gradImage(neighborIndex) != 0.0
00196              && edgeImage(neighborIndex) == false) {
00197             edgeImage(neighborIndex) = true;
00198             seedList.push_front(Index2D(static_cast<int>(row) + 1,
00199                                         static_cast<int>(column) + 1));
00200           }
00201         }
00202         return edgeImage;
00203       }
00204       
00205     } // namespace privateCode
00207 
00208     
00209     // This function applies the canny edge operator in the X
00210     // direction.
00211     template <ImageFormat FORMAT>
00212     Image<GRAY1>
00213     applyCanny(const Image<FORMAT>& inputImage,
00214                size_t gaussianSize,
00215                double upperThreshold,
00216                double lowerThreshold,
00217                double autoUpperThresholdFactor,
00218                double autoLowerThresholdFactor)
00219     {
00220       // Argument checking.
00221       if(inputImage.rows() < gaussianSize + 3
00222          || inputImage.columns() < gaussianSize + 3) {
00223         DLR_THROW(ValueException, "applyCanny()",
00224                   "Argument inputImage has insufficient size, or argument "
00225                   "gaussianSize is too large.");
00226       }
00227       if(lowerThreshold > upperThreshold) {
00228         DLR_THROW(ValueException, "applyCanny()",
00229                   "Argument lowerThreshold must be less than or equal to "
00230                   "Arguments upperThreshold.");
00231       }
00232       autoLowerThresholdFactor =
00233         std::min(autoLowerThresholdFactor, autoUpperThresholdFactor);
00234 
00235       // We use non-normalized convolution kernels for the gradient,
00236       // which means that our user-specified thresholds don't match
00237       // the magnitude of our gradients.  Each gradient component is
00238       // 8 times as large as it should be, so the magnitude of the
00239       // gradient is 8*sqrt(2) times as large.  We solve this by
00240       // scaling the thresholds here.
00241       lowerThreshold *= std::sqrt(2.0) * 8.0;
00242       upperThreshold *= std::sqrt(2.0) * 8.0;
00243       
00244       // Step 1: Blur with a gaussian kernel to reduce noise.
00245       Image<GRAY_FLOAT64> blurredImage;
00246       if(gaussianSize == 0) {
00247         blurredImage = convertColorspace<GRAY_FLOAT64>(inputImage);
00248       } else {
00249         Kernel<double> gaussian =
00250           getGaussianKernel<double>(gaussianSize, gaussianSize);
00251         blurredImage = filter2D<GRAY_FLOAT64, GRAY_FLOAT64>(
00252           gaussian, inputImage, 0.0);
00253       }
00254 
00255       // Step 2: Compute derivatives of the blurred image, and discard
00256       // any which are less than the lower threshold.
00257       Image<GRAY_FLOAT64> gradX = applySobelX(blurredImage);
00258       Image<GRAY_FLOAT64> gradY = applySobelY(blurredImage);
00259       Image<GRAY_FLOAT64> gradMagnitude(gradX.rows(), gradX.columns());
00260 
00261       if(lowerThreshold > 0.0 && upperThreshold > 0.0) {
00262         // Discard values less than the lower threshold.
00263         for(size_t index0 = 0; index0 < gradX.size(); ++index0) {
00264           double tmpVal = std::sqrt(
00265             gradX[index0] * gradX[index0] + gradY[index0] * gradY[index0]);
00266           gradMagnitude[index0] = (tmpVal > lowerThreshold) ? tmpVal : 0.0;
00267         }
00268       } else {
00269         // Temporarily retain all gradient values.
00270         for(size_t index0 = 0; index0 < gradX.size(); ++index0) {
00271           gradMagnitude[index0] = std::sqrt(
00272             gradX[index0] * gradX[index0] + gradY[index0] * gradY[index0]);
00273         }
00274 
00275         // Pick edge thresholds.
00276         size_t startRow = (gaussianSize + 1) / 2;
00277         size_t endRow = gradMagnitude.rows() - startRow;
00278         size_t startColumn = startRow;
00279         size_t endColumn = gradMagnitude.columns() - startColumn;
00280 
00281         // Paranoid check should never fail.
00282         if((startRow >= endRow) || (startColumn >= endColumn)) {
00283           DLR_THROW(ValueException, "applyCanny()",
00284                     "Filter kernel is too large for image.");
00285         }
00286         
00287         // Hack(xxx): Zero out borders of images to avoid spurious edges.
00288         for(size_t row = 0; row < startRow; ++row) {
00289           for(size_t column = 0; column < gradMagnitude.columns(); ++column) {
00290             gradMagnitude(row, column) = 0.0;
00291           }
00292         }
00293         for(size_t row = endRow; row < gradMagnitude.rows(); ++row) {
00294           for(size_t column = 0; column < gradMagnitude.columns(); ++column) {
00295             gradMagnitude(row, column) = 0.0;
00296           }
00297         }
00298         for(size_t row = 0; row < gradMagnitude.rows(); ++row) {
00299           for(size_t column = 0; column < startColumn; ++column) {
00300             gradMagnitude(row, column) = 0.0;
00301           }
00302           for(size_t column = endColumn; column < gradMagnitude.columns();
00303               ++column) {
00304             gradMagnitude(row, column) = 0.0;
00305           }
00306         }
00307 
00308         // Compute mean and variance of gradient values.
00309         size_t numberOfPixels =
00310           (endRow - startRow) * (endColumn - startColumn);
00311         Float64 sumOfGradient = 0.0;
00312         Float64 sumOfGradientSquared = 0.0;
00313         for(size_t row = startRow; row < endRow; ++row) {
00314           Float64 subSum = 0.0;
00315           Float64 subSumOfSquares = 0.0;
00316           for(size_t column = startColumn; column < endColumn; ++column) {
00317             Float64 testValue = gradMagnitude(row, column);
00318             // Changing how we compute threshold...
00319             //
00320             // if(testValue < minGrad) {minGrad = testValue;}
00321             // if(testValue > maxGrad) {maxGrad = testValue;}
00322             subSum += testValue;
00323             subSumOfSquares += testValue * testValue;
00324           }
00325           sumOfGradient += subSum;
00326           sumOfGradientSquared += subSumOfSquares;
00327         }
00328         double gradientMean = sumOfGradient / numberOfPixels;
00329         double gradientVariance =
00330           sumOfGradientSquared / numberOfPixels - gradientMean * gradientMean;
00331         double gradientSigma = std::sqrt(gradientVariance);
00332 
00333         if(upperThreshold <= 0.0) {
00334           upperThreshold =
00335             gradientMean + autoUpperThresholdFactor * gradientSigma;
00336           upperThreshold = std::max(upperThreshold, 0.0);
00337         }
00338         if(lowerThreshold <= 0.0) {
00339           lowerThreshold = 
00340             gradientMean + autoLowerThresholdFactor * gradientSigma;
00341           lowerThreshold = std::min(lowerThreshold, upperThreshold);
00342           lowerThreshold = std::max(lowerThreshold, 0.0);
00343         }
00344 
00345         // Now zero out gradients that for sure can never be edges.
00346         for(size_t index0 = 0; index0 < gradX.size(); ++index0) {
00347           double tmpVal = gradMagnitude[index0];
00348           gradMagnitude[index0] = (tmpVal > lowerThreshold) ? tmpVal : 0.0;
00349         }
00350       }
00351           
00352       // Step 3: Non-maximum suppression.
00353       Image<GRAY_FLOAT64> edgeCandidates =
00354         nonMaximumSuppress(gradMagnitude, gradX, gradY);
00355 
00356       // Step 4: Threshold with hysteresis.
00357       Image<GRAY1> edgeImage =
00358         privateCode::traceEdges(edgeCandidates, upperThreshold);
00359       return edgeImage;
00360     }
00361 
00362   } // namespace computerVision
00363   
00364 } // namespace dlr
00365 
00366 #endif /* #ifndef _DLRCOMPUTERVISION_KERNEL_H_ */

Generated on Wed Nov 25 12:15:04 2009 for dlrComputerVision Utility Library by  doxygen 1.5.8