Ring Stack Detection
UGRectRingPipeline
UGRectRingPipeline
The heart of the Ultimate Goal detector is the pipeline. A pipeline is just a fancy way describing the sequence of instructions given to continuously manipulate the image(in this case, what the camera sees). Ignoring the fancy code, the pipeline boils down to these following instructions:
Receiving the Input
In this line, what the camera sees is being passed in and represented as a matrix.
Manipulating the Input
The first line will convert the input matrix color space from RGB to YCrCb. Because the way YCrCb represents color by luminance(Y), chroma of red(CR), chroma of blue(Cb), it keeps values consistent under different lighting. Next we draw 2 rectangles on screen with predetermined position. The top rectangle should be where the 4th ring will be, and the bottom one should be where the first one will be. Then we extract the Cb value of each predetermined area of the ring to compare.
Finding CB Values
Here we crop the mat to just everything inside the two rectangles. Then we find the average of the values and store them in bottomAverage
and topAverage
.
Creating An Instance of UGRectDetector
UGRectDetector
The UGRectDetector
is a class that will show how you would use the pipeline. For more in-depth explanation of what everything does or more functionalities, please visit here. To start, create an instance of UGRectDetector
. The detector's constructor is overloaded. You can choose between:
hMap
: An instance of the hardware mapwebcamName
: The webcam name
If you use the first constructor, the detector will set the camera to the phone's camera. If the second is used, the webcam will be used.
Manipulating Detector Settings
You can change the orientation, width, and height of the camera for all instances (since the camera doesn't change between runs). You can update the settings by manipulating the static variables.
Setting Rectangle Positions
topRectHeightPercentage
: the percentage of the height of the user's input and should be a decimal under 1. It is used to calculate the first y value for the top rectangle.topRectWidthPercentage
: the percentage of the width of the user's input and should be a decimal under 1. It is used to calculate the first x value for the top rectangle.
bottomRectHeightPercentage
: the percentage of the height of the user's input and should be a decimal under 1. It is used to calculate the first y value for the bottom rectangle.bottomRectWidthPercentage
: the percentage of the width of the user's input and should be a decimal under 1. It is used to calculate the first x value for the bottom rectangle.
rectangleWidth
: the width of the rectangles in terms of pixelsrectangleHeight
: the height of the rectangles in terms of pixels
After creating an instance of the detector and setting the rectangle positions, continuously run DetectorInstance.getStack()
to get the number in the stack.
UGContourRingPipeline
UGContourRingPipeline
Vision Pipelines, the heart of any Ultimate Goal Detector. A pipeline is just a fancy way of saying a certain set of instructions that are applied to every inputted frame we see from the camera.
This is a Vision Pipeline utilizing Contours and an Aspect Ratio to determine the number of rings currently in the ring stack.
Using the Detector
The contour pipeline comes with a premade detector that can be used out of the box in your opmode. The UGContourRingDetector
is an object that runs the pipeline. You can manipulate the pipeline settings separately, but some of the settings of the detector get carried over to the pipeline.
To create an instance of the detector, there are different constructors, some with optional parameters.
You can, like with the rectangle detector, manipulate the settings of the contour detector.
To initialize the detector, simply call init
.
This initializes the pipeline with your configured settings. To retrieve the height determined by the pipeline, simply call DetectorInstance.getHeight()
.
Tuning
There are many values that the pipeline uses that can be changed/tuned to increase or decrease accuracy.
All configuration values are stored in a companion object
called Config (see here). In this, companion object
there are six variables, two of which are constants and cannot be changed.
lowerOrange
: the value of the lower orange used in finding the maskupperOrange
: the value of the upper orange used in finding the maskCAMERA_WIDTH
: the width of the resolution of the cameraHORIZON
: the value representing the horizon, on the y-axis. used in the horizon checkMIN_WIDTH
: the algorithmically generated value used in the minimum width checkBOUND_RATIO
: the value that the aspect ratio checks to determine whether the ring stack is one and four.
HORIZON
will most likely be the value you will have to tune. Its default value may be too restrictive or it may not. This value will show up on the image as a red line. Anything above this red line will be ignored in the pipeline's calculations. Make sure that the bottom line of the ring stack's contour box is below the horizon line.
NOTE: the returned image from the pipeline will be majority black, this is to show the logic behind what the pipeline is ignoring. All contours will be drawn in green. The binding rectangle of the largest contour below the horizon will be drawn in blue.
Config is stored in a companion object, this means that every instance of the UGContourRingPipeline
will share the same configuration.
These values and variables may change with future releases.
Below is the explanation of the algorithm the Pipeline uses
Receiving the Input
What the camera sees is being passed into the pipeline stored as an OpenCV Mat
type (short for matrix).
Manipulating the Input
Kotlin only: Mat?
is a nullable type of Mat
, since the inputted frame could be null.
We first take this Mat and convert it to YCrCb to help with thresholding.
We then perform an inRange operation on the input Mat and store the result in a temporary variable called mask. This mask is a black and white image where all white pixels on the mask were pixels in input that are in the orange range threshold. All black pixels on the mask were pixels in the input that were not in the orange range threshold.
Next, using a GaussianBlur, we eliminate any noise between the rings on the stack. Due to how we are thresholding in the mask calculation, there may be some gaps in the stack, due to shadows or unwanted light sources.
example of blurring in order to smooth images with Gaussian Blur: here
Finding the Contours
After the GaussianBlur, this noise is eliminated as the picture becomes "blurrier". We then find all contours on the image.
What is a Contour? Contours can be explained simply as a curve joining all the continuous points (along the boundary), having the same color or intensity. The contours are a useful tool for shape analysis and object detection and recognition.
example of contours: here
After finding the contours on the black and white mask, we then perform a linear search algorithm on the resulting list of contours (stored in as MatOfPoint). We first find the bounding rectangle of each contour and use this bounding rectangle (not rotated) to find the rectangle with the biggest width. We do this in order to not confuse the ring stack with other objects that may have been thought to be orange by the mask. Since the ring stack will most likely be the largest blob of orange in the view of the camera. When then do a check on the width of the widest contour. To see if it is actually a ring stack since zero is a valid option we must account for it. This check also makes sure that we don't mistake other smaller objects on the field as the ring stack, even if they are rings.
We also implemented a horizon check. Anything above the horizon is disregarded and not looked at even if it has the greatest width from all the other contours. This is to ensure that the algorithm down not detect the red goal as YCrCb color space is very unreliable when detecting the difference between red and orange.
Calculating the Aspect Ratio
After finding the widest contour, which is to be assumed the stack of rings, we perform an aspect ratio of the height over the width of the largest bounding rectangle.
Possible Questions:
Why not just count how tall the largest bounding rectangle is?
It is because of camera resolution. Since depending on the resolution of the camera, the height of the stack in pixels would be different despite them both being 4 (let's say for example).
Didn't you just say that you used a width check on the contour though? Isn't that also pixels?
Yes. we did, however, unlike the height of the stack, the width of the stack is consistent. It is always one ring wide, this way we are able to algorithmically generate a minimum bounding width.
Last updated