/// <summary> /// Differences the current image with the previous one and computes the number of pixels /// above the set difference threshold in the selected ROI /// </summary> /// <param name="imCurrent">The current frame to difference with the previous</param> /// <param name="roi">The ROI in which to perform the compuation</param> /// <returns>Number of pixels above threshold in the delta frame</returns> public int ComputeNumDeltaPixels(Image8 imCurrent, IppiROI roi) { double nPixels = 0; if (!_isFirst) { //form difference image cv.ippiAbsDiff_8u_C1R(imCurrent[roi.TopLeft], imCurrent.Stride, _imPrevious[roi.TopLeft], _imPrevious.Stride, _imDelta[roi.TopLeft], _imDelta.Stride, roi.Size); //threshold image BWImageProcessor.Im2Bw(_imDelta, _imThresh, roi, _threshold); //count pixels ip.ippiSum_8u_C1R(_imThresh[roi.TopLeft], _imThresh.Stride, roi.Size, &nPixels); } else { _isFirst = false; } //copy current image to previous image buffer ip.ippiCopy_8u_C1R(imCurrent[roi.TopLeft], imCurrent.Stride, _imPrevious[roi.TopLeft], _imPrevious.Stride, roi.Size); //return pixel count - divide sum by 255 since threshold sets al values to 255 return((int)nPixels / 255); }
/// <summary> /// Tracks the tailsegments on the supplied image and returns /// the angles and distances of each segment from the tailstart /// </summary> /// <param name="image">The image on which to id the tail</param> /// <returns>NSegments number of TailPoints</returns> public TailSegment[] TrackTail(Image8 image) { lock (_regionLock) { //CURRENTLY ONLY DOWNWARD FACING TAILS ARE PROPERLY VERIFIED!!! //Generate background by morphology operation - 10 times a second or whenever our coordinates changed //in the default case where the tail is darker than the background, using closing operation otherwise opening if (_frameNumber % (_frameRate / 10) == 0 || !_bgValid) { if (!_bgValid) { //if our regions changed, reblank our images! ip.ippiSet_8u_C1R(0, _background.Image, _background.Stride, _background.Size); ip.ippiSet_8u_C1R(0, _foreground.Image, _foreground.Stride, _foreground.Size); ip.ippiSet_8u_C1R(0, _thresholded.Image, _thresholded.Stride, _thresholded.Size); } if (_lightOnDark) { BWImageProcessor.Open(image, _background, _calc1, _strel, _trackRegionOuter); } else { BWImageProcessor.Close(image, _background, _calc1, _strel, _trackRegionOuter); } _bgValid = true; } //Compute foreground IppHelper.IppCheckCall(cv.ippiAbsDiff_8u_C1R(_background[_trackRegionInner.TopLeft], _background.Stride, image[_trackRegionInner.TopLeft], image.Stride, _foreground[_trackRegionInner.TopLeft], _foreground.Stride, _trackRegionInner.Size)); //Threshold BWImageProcessor.Im2Bw(_foreground, _thresholded, _trackRegionInner, _threshold); //Fill small holes BWImageProcessor.Close3x3(_thresholded, _thresholded, _calc1, _trackRegionOuter); } //Tracking concept: We track the angle of each segment end (TailStart+SegmentLength:TailEnd) //as the angular displacement from the previous segment end //To do this we define one full-circle with radius SegmentLength and an angle step corresponding //to ~1 Pixel. Then for each angle we pre-compute in InitializeScanPoints the corresponding x //and y offsets. From the full circle set we track -90 to +90 degrees around the angle of the //previous segment - for the initial segment that angle will be 0. For each segment we will return //the segment angle and its associated end-point coordinate var retval = new TailSegment[_nSegments]; lock (_scanPointLock) { //The index of the absolute angle of the previous segment in our angle sweep array //determines which angles we sweep over for the next segment int prevAngleIndex = _scanAngles.Length / 2; int nAnglesPerHalfPi = prevAngleIndex / 2;//this is the number of entries in the array that we have to walk to cover 90 degrees //we scan beginning with one segment length away from tail start and then walk //down the tail rather than using circles fixed arount the tailstart IppiPoint prevSegmentEnd = TailStart; //loop over tail segments for (int i = 0; i < _nSegments; i++) { //loop over scan-points from -pi/2 to +pi/2 (i.e. nAnglesPerHalfPi) around previous segment angle //interpreting the scan points as offsets around the previous tail segment //end point //then find tail angle of this segment int pointsFound = 0;//the number of non-zero pixels identified for (int j = -1 * nAnglesPerHalfPi; j < nAnglesPerHalfPi + 1; j++) { //Determine the index to scan - usually this will simply be "prevAngleIndex+j" however we //have to loop around our angle array properly in case we screen more extreme angles int currIndex = prevAngleIndex + j; if (currIndex >= _scanAngles.Length) { currIndex = currIndex % _scanAngles.Length; } else if (currIndex < 0) { currIndex = currIndex + _scanAngles.Length; } //If current point is outside of the image, ignore it IppiPoint pt = new IppiPoint(_coordinateOffsets[currIndex].x + prevSegmentEnd.x, _coordinateOffsets[currIndex].y + prevSegmentEnd.y); if (pt.x < 0 || pt.y < 0 || pt.x >= _imageSize.width || pt.y >= _imageSize.height) { continue; } //if the value at the current point >0 we mark that angle as valid //by storing the index in our anglestore if (*_thresholded[pt] > 0) { _angleStore[pointsFound] = currIndex; pointsFound++; } } //find the median point in our angle store if we have more than 2 points stored //the value in our angle store will directly give us the absolute screen angle of the segment //as well as the coordinate offset which we can used together with the previous segment endpoint //to compute the tail segment end coordinate - to get the delta angle we need to get the difference //between the stored index and the previously stored index, prevAngleIndex //after computing the appropriate return values we update prevAngleIndex with the index from the angle store //and prevSegmentEnd with the returned coordinate of the current tail segment double deltaTailAngle; IppiPoint coordinate; int pos; //the index in the angle store that we determine to be the tail-center if (pointsFound == 0) //we have lost the tail - no reason to keep tracking - fill remaining positions in array with NaNs for their angle { for (int k = i; k < _nSegments; k++) { deltaTailAngle = double.NaN; coordinate = new IppiPoint(); retval[k] = new TailSegment(deltaTailAngle, coordinate); } break; } else if (pointsFound < 3) { pos = _angleStore[0]; } else { //we want the angle at the median position of valid points //we don't compute intermediate positions however (we should be able to afford this since our angle step is so small), so in case //of an even number of points, the top of the lower half currently //wins - since array indexing is zero based, we have to subtract 1 //from the computed median position pos = _angleStore[(int)(Math.Ceiling(pointsFound / 2.0) - 1)]; } coordinate = new IppiPoint(prevSegmentEnd.x + _coordinateOffsets[pos].x, prevSegmentEnd.y + _coordinateOffsets[pos].y); deltaTailAngle = (pos - prevAngleIndex) * _angleStep; //the condition above of wrapping around the "angle circle":if (currIndex >= _scanAngles.Length).... //results in one very large (close to +360 or -360) angle of opposite sign at the switch point //however, since we usually only scan over offsets from +90 to -90 this case is easy to spot //and remedy: if (deltaTailAngle > 90) { deltaTailAngle = deltaTailAngle - 360;//this should create the appropriate tail angle btw. 0 and -90 } else if (deltaTailAngle < -90) { deltaTailAngle = 360 + deltaTailAngle;//this should create the appropriate tail angle btw. 0 and +90 } prevAngleIndex = pos; prevSegmentEnd = coordinate; retval[i] = new TailSegment(deltaTailAngle, coordinate); }//loop over tail segments } _frameNumber++; return(retval); }