/// <summary> /// Uses the parameters to reposition the images on the screen. /// </summary> /// <param name="skinObject"> Blob of skin </param> /// <param name="fingertipPositions"> fingertip coordinates </param> /// <param name="scale"> screen scale </param> public void ModelHand(SkinBlob skinObject, List <Vector2> fingertipPositions, float scale) { // Set palm position. palmImageRect.anchoredPosition = skinObject.GetMeanPoint() * scale; palmImageRect.sizeDelta = new Vector2((skinObject.GetWidth() / 2) * scale, (skinObject.GetWidth() / 2) * scale); palmImageCollider.radius = skinObject.GetWidth() / 2; // Add new fingertips if neccesary. if (fingertips.Count < fingertipPositions.Count) { var numberOfNewFingetips = fingertipPositions.Count - fingertips.Count; for (var i = 0; i < numberOfNewFingetips; i++) { fingertips.Add(Instantiate(fingertipPrefab, transform)); } } // Set location of fingertips. for (var i = 0; i < fingertips.Count; i++) { if (i < fingertipPositions.Count) { fingertips[i].gameObject.SetActive(true); fingertips[i].GetComponent <RectTransform>().anchoredPosition = fingertipPositions[i] * scale; } else { fingertips[i].gameObject.SetActive(false); } } CountFingers(fingertipPositions.Count); }
/// <summary> /// Tests if the point is within the threshold of being in another object. /// If not then create a new object. Also prunes skinblobs with tiny areas. /// </summary> /// <param name="point"> Location of pixel to check. </param> private void CheckSkinObjects(Vector2 point) { bool isObject = false; for (var i = 0; i < skinObjects.Count; i++) { if (skinObjects[i].TestPoint(point)) { isObject = true; if (skinObjects[i].GetArea() >= largestSkinObject.GetArea() && skinObjects[i].GetSize() >= largestSkinObject.GetSize()) { largestSkinObject = skinObjects[i]; } break; } else if (skinObjects[i].GetArea() <= 1.0f) { skinObjects.Remove(skinObjects[i]); i--; } } if (!isObject) { skinObjects.Add(new SkinBlob(point)); } }
/// <summary> /// Main segmentation loop. Loops through the entire set of pixels to /// detect skin-like pixels. Then Removes noise and enhances the skin /// pixels using skinblobs and gets the contourPointss of the skin. Finally /// calculating the convex hull. /// Also changes colors of pixels in the texture for debug / visualisation /// purposes. /// </summary> /// <param name="texture"> Contains all the pixels of the image. </param> /// <param name="threshold"> /// Contains the color threshold to be considered a skin candidate. /// </param> /// <returns> Edited texture. </returns> public Color[] SegmentColors(WebCamTexture texture, float threshold, DisplayOptions OPTIONS) { // Get an array of pixels from the texture. var pixels = texture.GetPixels(); // First Loop : Segment and identify skin blobs. skinObjects = new List <SkinBlob>(); largestSkinObject = new SkinBlob(new Vector2()); int close = 0; for (var i = 0; i < pixels.Length; i++) { // Identify skin coloured pixels and add them to an object. // Threshold for skin color lowers if the pixel is close to a definite skin pixel. if (PixelThreshold(pixels[i], threshold) || (close > 0 && PixelThreshold(pixels[i], threshold / 2))) { CheckSkinObjects(Utility.IndexToPoint(texture.width, i)); close = closeThreshold; // Display options - Sets initital segmentation pixels to white if (OPTIONS.SHOW_SEGMENTATION_FIRST) { pixels[i] = Color.white; } } else { close--; // Display options - Sets other pixels to black if (OPTIONS.SHOW_SEGMENTATION_FIRST) { pixels[i] = Color.black; } } } // Second Loop : focus on largest skin blob, removing noise. Get contour list. // Contour list creates candidates for the convex hull. var contourPoints = new List <Vector2>(); // Linewidth dictates the longest uninterrupted line of skin pixels. var lineWidth = 0; var thisPixel = 0; // 0 black / 1 white. var lastPixel = 0; // 0 black / 1 white. for (var i = 0; i < pixels.Length; i++) { var pixelCoords = Utility.IndexToPoint(texture.width, i); // If within the skin objects min max boundries. // Also within an even more lenient threshold. if (pixelCoords.y < largestSkinObject.GetMaxPoint().y&& pixelCoords.y > largestSkinObject.GetMinPoint().y&& pixelCoords.x < largestSkinObject.GetMaxPoint().x&& pixelCoords.x > largestSkinObject.GetMinPoint().x&& PixelThreshold(pixels[i], threshold / 3)) { lineWidth++; thisPixel = 1; // Skin pixel // Calculates 'center of mass'. largestSkinObject.AddToMean(pixelCoords); // Display Options - Show pixels after second segmentaiton. if (OPTIONS.SHOW_SEGMENTATION_SECOND) { pixels[i] = Color.grey; } } else { // Send linewidth to the skin object and reset linewidth as // black pixel has been reached. largestSkinObject.TestWidth(lineWidth); lineWidth = 0; thisPixel = 0; // Black pixel. // Display Options - Show pixels after second segmentaiton. if (OPTIONS.SHOW_SEGMENTATION_SECOND) { pixels[i] = Color.black; } } // Get a "good enough" contour point list for convex hull calculations. // Checks if the previous pixel was classed differently, then if true, // adds the current pixel to a list of contour points. if (thisPixel != lastPixel) { contourPoints.Add(pixelCoords); } lastPixel = thisPixel; } // Calculate convex hull using contour points. var hullPoints = ConvexHull.GetConvexHull(contourPoints); fingertipPoints = GetFingertips(hullPoints); // Display Options - Set contour pixels to green if (OPTIONS.SHOW_CONTOUR) { contourPoints.ForEach(point => { pixels[Utility.PointToIndex(texture.width, point)] = Color.green; }); } // Return array of edited pixels for display. return(pixels); }