public void ExtractSlots() { // Take snapshots of binary edge detection image and // read pixels and their colors from areas of interest //////////////////////////////////////////////////////// // Go through processing cameras for (int iCam = 0; iCam < numCams; ++iCam) { if (showDebugInfo) { Debug.Log("=========== " + transform.GetChild(iCam).name + " ==========="); } // Mark renderTexture active so that Texture2D.ReadPixels() // will get pixels from it instead of the entire viewport. Transform tShaderPlanes = transform.GetChild(iCam).GetChild(1); RenderTexture.active = (RenderTexture)tShaderPlanes. GetChild(tShaderPlanes.childCount - 3).GetChild(0). GetComponent <Renderer>().material.mainTexture; for (int iRow = 0; iRow < slotData[iCam].numberOfRows; ++iRow) { SlotData.Row row = slotData[iCam].rows[iRow]; for (int iSlot = 0; iSlot < row.numberOfSlots; ++iSlot) { SlotData.Slot slot = row.slots[iSlot]; if (slot.IsUndefined()) { // Slot corner points unassigned -> stop continue; } // Get a bounding rect to define the target texture dimension Rect boundingRect = slot.GetBoundingRect(); // Create a texture matching bounding rect dimensions slot.colorTexture = new Texture2D((int)boundingRect.width, (int)boundingRect.height); // Turn user-provided cornerpoint rect Y coordinates (texture top edge // Y == 0) into OpenGL coordinate space (texture bottom edge Y == 0) SwapY(ref boundingRect, transform.GetChild(iCam).GetSiblingIndex()); // Read pixels into texture memory slot.colorTexture.ReadPixels(boundingRect, 0, 0); // Retrieve pixel RGBA components Color32[] colorData = slot.colorTexture.GetPixels32(); /* * Pixel In Polygon algorithm: * - Search the bounding rectangle of the detection area quadrilateral. * In each Y position, traverse horizontally across the width of the * rectangle, a line at a time. If at any point the pixel under scrutiny * has crossed - from left to right - an uneven number of detection * area edge lines, it must be within the detection area. In such case, * if the color in that location is white, consider a hittest a success. */ // Construct line data from detection area corner points List <Line> lines = new List <Line>(); for (int iCp = 0; iCp < slot.areaCornerpoints.Length; ++iCp) { // Get each adjacent corner point pair SlotData.Cornerpoint cp = slot.areaCornerpoints[iCp]; SlotData.Cornerpoint nextCp = slot.areaCornerpoints[(iCp + 1) % slot.areaCornerpoints.Length]; // Do OpenGL Y coord swapping SwapY(ref cp.y, transform.GetChild(iCam).GetSiblingIndex()); SwapY(ref nextCp.y, transform.GetChild(iCam).GetSiblingIndex()); // Use the bounding rect lower (OpenGL) left corner coordinate as the // (0, 0) reference point to create a line from a pair of corner points lines.Add(new Line( cp.x - (int)boundingRect.x, cp.y - (int)boundingRect.y, nextCp.x - (int)boundingRect.x, nextCp.y - (int)boundingRect.y)); } // Start counting the total and relative amount of white pixels /////////////////////////////////////////////////////////////// int numTotalPx = 0; // int numTotalPx = Mathf.RoundToInt(boundingRect.width * boundingRect.height); int numWhiteFound = 0; // Go through a black/white binary-processed source image pixel at a time for (int iColor = 0; iColor < colorData.Length; ++iColor) { int crossedLineCount = 0; // Get X and Y coordinates being checked int textureX = iColor % (int)boundingRect.width; int textureY = Mathf.FloorToInt(iColor / boundingRect.width); // Go through detection area edge lines, looking for line crossings. // Order of scanning will be according to OpenGL coordinate space, // from bottom (y == 0) to top (Y == texture height). foreach (Line line in lines) { if (line.IsPointYInRange(textureY)) { // The coordinate is within the Y axis range of the edge line. // Get the X position on the line in the Y coordinate. float lineCrossingX = line.GetXFromY(textureY); // Compare line X with the X of the location being checked if (System.Single.IsNaN(lineCrossingX)) { // Y coordinate matches horizontal detection area edge line // => pixel is to be considered to be inside the area and // taken into account. crossedLineCount = 1; break; } else if (lineCrossingX <= textureX) { // Texture X coordinate is to the right of a line crossing ++crossedLineCount; } } } if (IsOddLineCount(crossedLineCount)) { // Increase total found amount ++numTotalPx; if (IsWhiteColor(colorData[iColor])) { ++numWhiteFound; } } } // Pixel color count done -> calculate results int thresholdPixelCount = (int)(slot.pxThresholdLevel * numTotalPx); if (showDebugInfo) { Debug.Log("Parking row # " + (iRow + 1) + ", parking slot # " + (iSlot + 1) + ": " + "White pixel / total area pixel count & percentage: " + numWhiteFound + "/" + numTotalPx + ", " + ((float)numWhiteFound / numTotalPx * 100f).ToString("0.00") + "%" + "\nFree-to-reserved white pixel threshold count & percentage: " + thresholdPixelCount + ", " + (slot.pxThresholdLevel * 100).ToString("0.00") + "% " + (numWhiteFound > thresholdPixelCount ? "RESERVED" : "FREE")); } // Inform scene maintenance script about slot reservation state driveLineController.rows[iRow][iSlot] = numWhiteFound > thresholdPixelCount; } // for (int iSlot = 0; iSlot < row.numberOfSlots; ++iSlot) } // for (int iRow = 0; iRow < slotData.numberOfRows; ++iRow) } // for (int iCam = 0; iCam < numCams - 1; ++iCam) }
public void SetCornerpointDisplayData(Material shaderMaterial) { cpList = new List <SlotData.Cornerpoint>(); colorList = new List <Color>(); if (displayEdgeDetectionAreas) { // Edge detection cornerpoints have been selected to show up in the inspector if (slotScript.slotData.numberOfRows == 0) { if (!hasWarningBeenIssued) { Debug.LogWarning("No detection area rows defined for " + transform.parent.parent.name + "."); // Limit warnings to one per each playback hasWarningBeenIssued = true; } return; } // Get corner points for each area for (int iRow = 0; iRow < slotScript.slotData.numberOfRows; ++iRow) { SlotData.Row row = slotScript.slotData.rows[iRow]; for (int iSlot = 0; iSlot < row.numberOfSlots; ++iSlot) { if (row.slots[iSlot].IsUndefined()) { // Unset slot coordinates => skip continue; } cpList.AddRange(row.slots[iSlot].areaCornerpoints); } } // Use texture to pass corner point data to the shader ////////////////////////////////////////////////////// // 4 corner points for each defined slot // Each pixel value in texture can hold data for two points, // (== half a slot), i.e., sizeof(RGBA) == 2 * sizeof(XY). // Therefore, each slot will consume two pixels. // Slot N (Tex line N) Tex px N Px cpnts Slot cornerpoints // 0 0 RG (X, Y) // 0 0 BA (X, Y) // 0 1 RG (X, Y) // 0 1 BA (X, Y) // ----------------------------------------------- // 1 2 RG (X, Y) // 1 2 BA (X, Y) // 1 3 RG (X, Y) // 1 3 BA (X, Y) // ----------------------------------------------- // etc. // Total number of slots in all parking rows int numSlotsTotal = cpList.Count / 4; // Number of required texture pixels to represent // coordinate points for every slot detection area // (one RGBA covers 2 out of 4 cornerpoints for a slot) int texturePixelCount = numSlotsTotal * 2; // Create texture that can hold corner pixel location data tex = new Texture2D(texturePixelsPerLine, numSlotsTotal); Color color = new Color(0f, 0f, 0f, 0f); for (int iPixel = 0; iPixel < texturePixelCount; ++iPixel) { // Two cornerpoints fit in the RGBA structure for (int iCp = 0; iCp < 2; ++iCp) { SlotData.Cornerpoint cp = cpList[iPixel * 2 + iCp]; // Swap Y coordinate and interpolate pixel values imageSlotExtractor.SwapY(ref cp.y, transform.parent.parent.GetSiblingIndex()); Vector2 interpCp = new Vector2( (float)cp.x / planeManager.GetSourceWidth(), (float)cp.y / planeManager.GetSourceHeight()); // Store interpolated [0...1] values. if (iCp % 2 == 0) { // First coordinate pair that populates RG of RGBA color.r = interpCp.x; color.g = interpCp.y; } else { // Second coordinate pair that populates BA of RGBA color.b = interpCp.x; color.a = interpCp.y; colorList.Add(color); } } } // Setting filter mode to Point is a MUST! With default setting, // there's a LOT of pixel averaging and/or blending. We want // pure blocky content! tex.filterMode = FilterMode.Point; // Apply color data to the texture tex.SetPixels(colorList.ToArray()); tex.Apply(); // Pass texture data to the shader /////////////////////////////////// // Texture shaderMaterial.SetTexture("_ColorTex", tex); // Texture dimensions shaderMaterial.SetInt("_ColorTexWidth", texturePixelsPerLine); shaderMaterial.SetInt("_ColorTexHeight", numSlotsTotal); // Alternating colors for adjacent sets of four cornerpoints shaderMaterial.SetColor("_CornerColor0", cornerPointColorA); shaderMaterial.SetColor("_CornerColor1", cornerPointColorB); // Source image aspect ratio shaderMaterial.SetFloat("_MainTexAspectRatio", (float)planeManager.GetSourceWidth() / planeManager.GetSourceHeight()); // Radius of colored cornerpoint square in the screen shaderMaterial.SetFloat("_CornerpointRadius", edgeCornerPointDisplayRadius); } // Enable variable needs to be passed outside of conditional block to ensure once enabled // borders are erased during playback if the user unselects the option in the inspector shaderMaterial.SetFloat("_DisplayCornerpoints", displayEdgeDetectionAreas ? 1.0f : 0.0f); }