Skip to content

DetectCircularSymmetry

markmaker edited this page Mar 2, 2024 · 24 revisions

Operating Principle

The Computer Vision pipeline stage DetectCircularSymmetry searches the image for Circular Symmetry i.e. for center points where the pixels of individual surrounding concentric rings show little change (variance) when compared to the overall variance in the same area.

As an example for Circular Symmetry, consider an archery target:

Archery target

Source: Wikipedia about Circular Symmetry

The stage can be used to detect concentric things, like fiducials, sprocket holes, nozzle tips in runout calibration etc.

As an input, it takes the expected diameter (minimum and maximum) of the feature to be detected. This is often a property that is well known or can easily be measured (using a caliper) without special knowledge about computer vision.

Unlike other stages like DetectCirclesHough, or the Countours family of stages, it does not require sharp edges, thresholds, filtering, footprint colors etc. internally or as a prerequisite. Instead, it simply compares the image to itself, so it is entirely self-tuning, and it can robustly work with very weak contrasts, very soft images and changing ambient lights, varried/inverted colors.

Example Images and Use Cases

Here is a series of images before/after detection. All these are with the same standard pipeline, only adjusted for nozzle tip/fiducial diameter, no tuning:

Runout calibration with dark Samsung CP40 nozzle tips:

circular-symmetry-nozzle-tip

Same pipeline with a very low contrast, very soft/out of focus nozzle tip:

circular-symmetry-blur

Off-center view for camera position/rotation calibration: The nozzle tip is seen slightly from the side which means the various features of the nozzle are no longer concentric. The air bore is also slightly asymmetric. By simply selecting the wanted min/max diameter range it automatically snaps to the right ring. Observe how the blue center point jumps:

circular-symmetry-diameter-selection

The same exact pipeline used for a fiducial:

circular-symmetry-fiducial

Diagnostics

For diagnostic purposes you can enable a heat map, indicating the degree of symmetry:

circular-symmetry-diagnostics

Editing the Stage in the Pipeline Editor

The standard pipeline is very simple (nozzle tip calibration example):

Pipeline Editor

The DetectCircularSymmetry stage is configured as follows:

General Properties

diagnostics: Switches on an overlaid heat map of Circular Symmetry.

minSymmetry: Minimum relative circular symmetry. Defaults to 1.2. Any match below this ratio is considered invalid. The score value is calculated as the ratio of overall area pixel variance divided by the sum of pixel ring variances. These are variances weighed by area i.e. by pixel count. Because the image is compared against itself, this ratio is very stable, i.e. it should not be necessary to tune this.

subSampling: To improve performance, only one pixel out of a block of subSampling × subSampling pixels is sampled. The preliminary winner is then locally searched using iteration with smaller and smaller subSampling size. The subSampling value will automatically be reduced for small diameters. Use BlurGaussian before this stage if subSampling suffers from interference/moiré effects.

superSampling: Can be used to achieve sub-pixel final precision: 1 means no super-sampling, 2 means half sub-pixel precision etc. Practical up to ~8.

maxTargetCount: Maximum number of targets (e.g. sprocket holes) to be found. The targets with best symmetry score are returned. Overlap is automatically eliminated.

corrSymmetry: Correlated minimum circular symmetry for multiple matches, i.e. other matches must have at least this relative symmetry. Note that the symmetry score is very dynamic, so setting a robust correlation is difficult.

symmetryScore provides different score functions to reject partial/interrupted ring edges. These may be selected, when the detection has to compete with other nearby features that happen to have an (interrupted) tangential match with the wanted circular diameters. Typical example: on paper carrier tapes 0402 pockets are confused with sprocket holes. Another example are texture patterns that happen to have a circular weight either randomly or from LED reflection highlights etc. (for both examples see the animation below). By either taking the average or median value of ring pixels/segments, the comparison variance is reduced when rings are interrupted, making a candidate less favorable.

circular-symmetry-score

  • Overall variance vs. ring variance sum (default): tolerant circular symmetry score. Matches partial/scattered circular patterns.
  • Ring ageraged variance vs. ring variance sum: stricter circular symmetry score. The rings must be quite uniform to match.
  • Ring median variance vs. ring variance sum: even stricter circular symmetry score. Rejects interrupted rings, e.g. from tangential non-circular edges.

Properties used when controlled by Vision Operations

propertyName: Sets the property name, as set by vision operations using this pipeline. Currently, this is nozzleTip for the nozzle tip calibration and fiducial for the fiducial locator. As soon as the named property is set by the vision operation, the stage is dynamically controlled and no longer uses the fixed properties in the stage itself. Proper camera Units Per Pixel scaling makes sure the dynamic properties work across machines/cameras/resolutions.

innerMargin: The inner margin, relative to the diameter. Only used when the propertyName is set i.e. when the nominal diameter is supplied by vision operations. The minDiameter is then computed as diameter*(1 - innerMargin).

outerMargin: The outer margin, relative to the diameter. Only used when the propertyName is set i.e. when the nominal diameter is supplied by vision operations. The maxDiameter is then computed as diameter*(1 + outerMargin).

Properties used when controlled by the Stage

minDiameter: Minimum diameter, in pixels, of the wanted circular feature. This should always be a bit smaller than the actual diameter, because the symmetry analysis needs some margin area to detect circular image gradients.

maxDiameter: Maximum diameter, in pixels, of the wanted circular feature. This should always be a bit larger than the actual diameter, because the symmetry analysis needs some margin area to detect radial image gradients.

maxDistance: Maximum search distance (radius), in pixels, from the nominal location of the feature (usually from the center of the camera view). The search distance will be cropped to the image margin.

searchWidth: Maximum search width, in pixels, across the nominal location of the feature. If 0 is given, 2 × maxDistance is taken i.e. there is no limit applied.

searchHeight: Maximum search height, in pixels, across the nominal location of the feature. If 0 is given, 2 × maxDistance is taken i.e. there is no limit applied.

Vision Operations Control

To supply the wanted diameter and maximum search distance from the vision operations, some new GUI properties have been added and other properties parametrized to the stage.

Nozzle Tip Calibration

Nozzle Tip Calibration

Vision Diameter: Must be set to the physical diameter of the feature you want to detect. Typically, a feature at the very point of the nozzle tip. Either the outer diameter or the air bore.

Measure the Vision Diameter

Measuring the Vision Diameter using the camera and relative DRO mode (see also the animation below):

  1. Center the nozzle tip.
  2. Zoom the Camera View using scroll-mouse.
  3. Jog to the right edge of the wanted circular feature.
  4. Click the DRO to put it in Relative Mode (it turns from greenish to blueish).
  5. Jog to the left edge of the wanted circular feature.
  6. Read the diameter off the DRO X coordinate.
  7. Enter as Vision Diameter.

Measuring the nozzle tip feature diameter avi

The maxDistance property is automatically derived from the already present Offset Threshold.

Standard pipeline (Edit the pipeline and paste this using the Paste button):

<cv-pipeline>
   <stages>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageCapture" name="0" enabled="true" default-light="false" settle-first="true" count="1">
         <light class="java.lang.Double">128.0</light>
      </cv-stage>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb1" enabled="false" prefix="runout_calibration_source_" suffix=".png"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.BlurGaussian" name="2" enabled="false" kernel-size="7"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DetectCircularSymmetry" name="results" enabled="true" min-diameter="30" max-diameter="50" max-distance="200" max-target-count="1" min-symmetry="1.2" corr-symmetry="0.0" property-name="nozzleTip" outer-margin="0.2" inner-margin="0.4" sub-sampling="8" super-sampling="1" diagnostics="false"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DrawCircles" name="4" enabled="true" circles-stage-name="results" thickness="2">
         <color r="255" g="0" b="51" a="255"/>
         <center-color r="0" g="204" b="255" a="255"/>
      </cv-stage>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb1" enabled="false" prefix="runout_calibration_result_" suffix=".png"/>
   </stages>
</cv-pipeline>

Fiducial Locator

This is now the standard pipeline in newer OpenPnP versions.

Fiducial Locator

The diameter of the fiducial is automatically derived from the footprint. Therefore, the same underlying data can be used both for the classic MatchPartTemplate based pipeline and the new pipeline (obviously this only works if the ficucial is actually round).

Max. Distance: Maximum allowed distance between nominal fiducial location and detected fiducial location. It controls the search distance in the stage. However, this is also active with other pipelines/stages i.e. it also acts as an added sanity check: If this distance is exceeded, the locator will throw an exception.

Standard pipeline (Edit the pipeline and paste this using the Paste button):

<cv-pipeline>
   <stages>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageCapture" name="image" enabled="true" default-light="true" settle-first="true" count="1"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb0" enabled="false" prefix="fidloc_source_" suffix=".png"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.BlurGaussian" name="blur" enabled="false" kernel-size="3"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DetectCircularSymmetry" name="circular" enabled="true" min-diameter="10" max-diameter="100" max-distance="200" min-symmetry="1.2" property-name="fiducial" outer-margin="0.2" inner-margin="0.4" sub-sampling="8" diagnostics="false"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ConvertModelToKeyPoints" name="results" enabled="true" model-stage-name="circular"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DrawCircles" name="draw" enabled="true" circles-stage-name="circular" thickness="1">
         <color r="255" g="255" b="0" a="255"/>
         <center-color r="255" g="153" b="0" a="255"/>
      </cv-stage>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb1" enabled="false" prefix="fidloc_results_" suffix=".png"/>
   </stages>
</cv-pipeline>

ReferenceStripFeeder

The ReferenceStripFeeder sets the diameter to be detected, i.e. the sprocket hole diameter according to the EIA 481 standard. A relatively accurate camera units per pixel setting is required, i.e. use Issues & Solutions camera calibration, which includes 3D Units per Pixel, so the feeder Z is taken into consideration. Make sure to set Z accurately.

The ReferenceStripFeeder controls the maxDistance search range: full camera scope for Auto Setup and an optimized minimal search range for routine detection of the nearest hole.

Edit the pipeline and paste this using the Paste button:

<cv-pipeline>
   <stages>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageCapture" name="original" enabled="true" default-light="true" settle-first="true" count="1"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb0" enabled="false" prefix="strip_" suffix=".png"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.BlurGaussian" name="predetect-1" enabled="false" kernel-size="5"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DetectCircularSymmetry" name="results" enabled="true" min-diameter="10" max-diameter="100" max-distance="100" max-target-count="20" min-symmetry="1.2" corr-symmetry="0.25" property-name="sprocketHole" outer-margin="0.3" inner-margin="0.1" sub-sampling="8" super-sampling="2" symmetry-score="RingMedianVarianceVsRingVarianceSum" diagnostics="false"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageRecall" name="recalled" enabled="false" image-stage-name="original"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DrawCircles" name="display" enabled="true" circles-stage-name="results" thickness="1">
         <color r="255" g="0" b="0" a="255"/>
      </cv-stage>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb1" enabled="false" prefix="strip_result_" suffix=".png"/>
   </stages>
</cv-pipeline>

This was tested with deliberately difficult contrast and reflective background. The inner margin is set to only 0.05, forcing the stage to detect the outer edge, i.e. perspective and shadow bias is effectively minimized:

Strip Feeder

A deliberately bent strip was perfectly followed, zero mis-detects:

Bent Strip

ReferencePushPullFeeder

The ReferencePushPullFeeder sets the diameter to be detected, i.e. the sprocket hole diameter according to the EIA 481 standard. A relatively accurate camera units per pixel setting is required, i.e. use Issues & Solutions camera calibration, which includes 3D Units per Pixel, so the feeder Z is taken into consideration.

The ReferencePushPullFeeder also controls the maxDistance search range: full camera scope for Auto Setup and an optimized minimal search range for routine detection of the two reference holes.

Edit the pipeline and paste this using the Paste button:

<cv-pipeline>
   <stages>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageCapture" name="0" enabled="true" default-light="true" settle-first="true" count="1"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb0" enabled="false" prefix="push_pull_" suffix=".png"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.BlurGaussian" name="1" enabled="true" kernel-size="5"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.AffineWarp" name="2" enabled="true" length-unit="Millimeters" x-0="0.0" y-0="0.0" x-1="0.0" y-1="0.0" x-2="0.0" y-2="0.0" scale="1.0" rectify="true" region-of-interest-property="regionOfInterest"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ConvertColor" name="3" enabled="true" conversion="Bgr2Gray"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.SimpleOcr" name="OCR" enabled="true" alphabet="0123456789.-+_RCLDQYXJIVAFH%GMKkmuµnp" font-name="Liberation Mono" font-size-pt="7.0" font-max-pixel-size="20" auto-detect-size="false" threshold="0.75" draw-style="OverOriginalImage" debug="false"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageRecall" name="5" enabled="true" image-stage-name="0"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DetectCircularSymmetry" name="results" enabled="true" min-diameter="10" max-diameter="100" max-distance="100" max-target-count="10" min-symmetry="1.2" corr-symmetry="0.2" property-name="sprocketHole" outer-margin="0.2" inner-margin="0.2" sub-sampling="8" super-sampling="1" symmetry-score="RingMedianVarianceVsRingVarianceSum" diagnostics="false"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageRecall" name="7" enabled="false" image-stage-name="0"/>
      <cv-stage class="org.openpnp.vision.pipeline.stages.DrawCircles" name="8" enabled="false" circles-stage-name="results" thickness="1">
         <color r="255" g="0" b="0" a="255"/>
      </cv-stage>
      <cv-stage class="org.openpnp.vision.pipeline.stages.ImageWriteDebug" name="deb1" enabled="false" prefix="push_pull_result_" suffix=".png"/>
   </stages>
</cv-pipeline>

Tested with difficult transparent tape, as always zero tuning required:

Push Pull Feeder

The exact same pipeline also works with a paper tape:

Paper tape

Note, the pipeline no longer requires a saturated "green-screen" color, it just uses whatever contrast/color difference there is. But it does account for all color channels in the symmetry computation.

Clone this wiki locally