/// <summary> /// Searches the image for the good features to track. /// <para>For each location a Hessian matrix is made and min eig-value is compared against threshold.</para> /// </summary> /// <param name="image">Image.</param> /// <param name="winSize">Window size.</param> /// <param name="minEigVal">Minimum eigen value.</param> /// <param name="minimalDistance">Minimum distance from two features.</param> /// <returns>List of locations that have eigen value larger than <paramref name="minEigVal"/>.</returns> public static List <Point> GoodFeaturesToTrack(this Gray <float>[,] image, int winSize = 10, float minEigVal = 0.3f, float minimalDistance = 3) { var strengthImg = image.CopyBlank(); var Dx = image.Sobel(1, 0, 3); var Dy = image.Sobel(0, 1, 3); var Dxx = Dx.MulFloat(Dx).MakeIntegral(); var Dxy = Dx.MulFloat(Dy).MakeIntegral(); var Dyy = Dy.MulFloat(Dy).MakeIntegral(); goodFeaturesToTrack(Dxx, Dxy, Dyy, winSize, minEigVal, strengthImg); var filteredStrengthImg = strengthImg.SupressNonMaxima(); //var filteredStrengthImg = strengthImg; IList <float> values; var locations = filteredStrengthImg.FindNonZero(out values); var sortedFeatures = locations.Zip(values, (f, s) => new { f, s }) .OrderByDescending(x => x.s) .Select(x => x.f) .ToList(); sortedFeatures = sortedFeatures.EnforceMinimalDistance(minimalDistance); return(sortedFeatures); }
/// <summary> /// Does non-maxima supression for the following gray image. Can be useful for detections filtering (e.g. post-processing output from Harris detector). /// </summary> /// <param name="img">Image.</param> /// <param name="radius">Non-maxima supression radius (kernel radius).</param> /// <param name="discardValue">The value will be discarded (0 - for black).</param> /// <returns>Processed image.</returns> public static Gray <float>[,] SupressNonMaxima(this Gray <float>[,] img, int radius = 3, int discardValue = 0) { var dest = img.CopyBlank(); SupressNonMaxima(img, dest, radius); return(dest); }
/// <summary> /// Computes gradient orientations from the color image. Orientation from the channel which has the maximum gradient magnitude is taken as the orientation for a location. /// </summary> /// <param name="frame">Image.</param> /// <param name="magnitudeSqrImage">Squared magnitude image.</param> /// <param name="minValidMagnitude">Minimal valid magnitude.</param> /// <returns>Orientation image (angles are in degrees).</returns> public static unsafe Gray<int>[,] Compute(Bgr<byte>[,] frame, out Gray<int>[,] magnitudeSqrImage, int minValidMagnitude) { var minSqrMagnitude = minValidMagnitude * minValidMagnitude; var orientationImage = new Gray<int>[frame.Height(), frame.Width()]; var _magnitudeSqrImage = orientationImage.CopyBlank(); using (var uFrame = frame.Lock()) { ParallelLauncher.Launch(thread => { computeColor(thread, (byte*)uFrame.ImageData, uFrame.Stride, orientationImage, _magnitudeSqrImage, minSqrMagnitude); }, frame.Width() - 2 * kernelRadius, frame.Height() - 2 * kernelRadius); } magnitudeSqrImage = _magnitudeSqrImage; return orientationImage; }
private static Gray<byte>[,] SpreadOrientations(Gray<byte>[,] quantizedOrientationImage, int neghborhood) { var destImg = quantizedOrientationImage.CopyBlank(); using (var uQuantizedOrientationImage = quantizedOrientationImage.Lock()) using(var uDestImg = destImg.Lock()) { byte* srcImgPtr = (byte*)uQuantizedOrientationImage.ImageData; int imgStride = uQuantizedOrientationImage.Stride; byte* destImgPtr = (byte*)uDestImg.ImageData; int imgHeight = uDestImg.Height; int imgWidth = uDestImg.Width; for (int row = 0; row < neghborhood; row++) { int subImageHeight = imgHeight - row; for (int col = 0; col < neghborhood; col++) { OrImageBits(&srcImgPtr[col], destImgPtr, imgStride, imgWidth - col, subImageHeight); } srcImgPtr += imgStride; } } return destImg; }
/// <summary> /// Take only those orientations that have MINIMAL_NUM_OF_SAME_ORIENTED_PIXELS in 3x3 negborhood. /// Performs angle transformation into binary form ([0..7] -> [1, 2, 4, 8, ..., 128]) as well. /// </summary> /// <param name="qunatizedOrientionImg">Quantized orientation image where angles are represented by lables [0..GlobalParameters.NUM_OF_QUNATIZED_ORIENTATIONS] (invalid orientation label included).</param> /// <param name="minSameOrientations">Minimal number of same orientations for 3x3 neigborhood. The range is: [0..9] (3x3 neigborhood).</param> private static Gray<byte>[,] RetainImportantQuantizedOrientations(Gray<byte>[,] qunatizedOrientionImg, int minSameOrientations) { if (minSameOrientations < 0 || minSameOrientations > 9 /*3x3 neigborhood*/) throw new Exception("Minimal number of same orientations should be in: [0..9]."); var quantizedFilteredOrient = qunatizedOrientionImg.CopyBlank(); using (var uQunatizedOrientionImg = qunatizedOrientionImg.Lock()) using (var uQuantizedFilteredOrient = quantizedFilteredOrient.Lock()) { //debugImg = new Image<Hsv, byte>(orientDegImg.Width, orientDegImg.Height); //debugImg = null; int qOrinetStride = uQunatizedOrientionImg.Stride; int qOrinetAllign = uQunatizedOrientionImg.Stride - uQunatizedOrientionImg.Width; byte* qOrinetUnfilteredPtr = (byte*)uQunatizedOrientionImg.ImageData + qOrinetStride + 1; byte* qOrinetFilteredPtr = (byte*)uQuantizedFilteredOrient.ImageData + qOrinetStride + 1; //Debug.Assert(qunatizedOrientionImg.Stride == quantizedFilteredOrient.Stride); int imgWidth = uQunatizedOrientionImg.Width; int imgHeight = uQunatizedOrientionImg.Height; for (int j = 1; j < imgHeight - 1; j++) { for (int i = 1; i < imgWidth - 1; i++) { if (*qOrinetUnfilteredPtr != INVALID_QUANTIZED_ORIENTATION) { byte[] histogram = new byte[INVALID_QUANTIZED_ORIENTATION + 1]; //gleda se susjedstvo 3x3 histogram[qOrinetUnfilteredPtr[-qOrinetStride - 1]]++; histogram[qOrinetUnfilteredPtr[-qOrinetStride + 0]]++; histogram[qOrinetUnfilteredPtr[-qOrinetStride + 1]]++; histogram[qOrinetUnfilteredPtr[-1]]++; histogram[qOrinetUnfilteredPtr[0]]++; histogram[qOrinetUnfilteredPtr[+1]]++; histogram[qOrinetUnfilteredPtr[+qOrinetStride - 1]]++; histogram[qOrinetUnfilteredPtr[+qOrinetStride + 0]]++; histogram[qOrinetUnfilteredPtr[+qOrinetStride + 1]]++; int maxBinVotes = 0; byte quantizedAngle = 0; for (byte histBinIdx = 0; histBinIdx < GlobalParameters.NUM_OF_QUNATIZED_ORIENTATIONS /*discard invalid orientation*/; histBinIdx++) { if (histogram[histBinIdx] > maxBinVotes) { maxBinVotes = histogram[histBinIdx]; quantizedAngle = histBinIdx; } } if (maxBinVotes >= minSameOrientations) *qOrinetFilteredPtr = (byte)(1 << quantizedAngle); //[1,2,4,8...128] (8 orientations) //*qOrinetFilteredPtr = (byte)(1 << *qOrinetUnfilteredPtr); //[1,2,4,8...128] (8 orientations) //debugImg[j, i] = new Hsv((*qOrinetFilteredPtr-1) * 35, 100, 100); } qOrinetUnfilteredPtr++; qOrinetFilteredPtr++; } qOrinetUnfilteredPtr += 1 + qOrinetAllign + 1; qOrinetFilteredPtr += 1 + qOrinetAllign + 1; //preskoči zadnji piksel, poravnanje, i početni piksel } } //magnitudeImg.Save("magnitude.bmp"); //quantizedFilteredOrient.Save("quantizedImg.bmp"); return quantizedFilteredOrient; }