C++ Klasse zum Ermitteln von Erkennungsmarken mit OpenCV
C++ class template for fiducial alignment detection using OpenCV
Mit dieser Klasse können Kreisförmige Erkennungsmarken von Kamerabildern erkannt und vermessen werden. Die Dokumentation befindet sich im weiter unten angegebenen Quelltext. Die Bildverarbeitung geschieht mithilfe von OpenCV.
This class enables detecting and measuring circular fiducials/alignment marks from captured camera images. The documentation can be found in the source code below. The image processing is done using OpenCV.
Dateien
Files
fiducial_capture.hh sw/vpu/cvimage.hh sw/vpu/circlefit.hh
Example output video: camcap.mp4 ("save as ...")
Beispiel
Example
/**
* Example program for fiducial_capture.hh.
*
*
*/
#include "fiducial_capture.hh"
#include <iostream>
#include <iomanip>
using namespace std;
// The fiducial capture class and instance ...
typedef sw::im::fiducial_capture fcap_t;
fcap_t cap;
/**
* Setting handling, so that increment and decrement can be done
* using the up/down keys.
*/
struct setting
{
inline setting(double i) : value(i), min(i), max(i), step(0), ident("?") {;}
inline setting(double initial, double min_, double max_, double step_, const char* ident) :
value(initial), min(min_), max(max_), step(step_), ident(ident) {;}
inline setting(const setting& s) : value(s.value), min(s.min), max(s.max), step(s.step) {;}
inline setting& operator=(const setting& s) {value = s.value; return *this; }
inline setting& operator=(const double& v) {value = v; return *this; }
inline setting& operator ++() { value += step; if(value > max) value=max; return *this; }
inline setting& operator --() { value -= step; if(value < min) value=min; return *this; }
inline operator double () { return value; }
void disp() { std::cout << ident << "=" << value << std::endl; }
double value, min, max, step;
const char* ident;
};
// Settings definition:
setting cfg_leveling(0, 0, 0.45, 0.05, "leveling");
setting cfg_blur(5, 0, 30, 1, "blur");
setting cfg_canny_threshold(80, 0, 250, 10, "canny_threshold");
setting cfg_num_avgerage_images(2, 0, 10, 1, "average_images");
setting cfg_circularity(80, 0, 100, 1, "circularity");
setting cfg_min_diameter(5, 5, 90, 5, "min_radius");
setting cfg_max_diameter(100, 20, 100, 5, "max_radius");
setting max_radius_stddev(10, 0, 100, 5, "max_radius_stddev");
setting num_tunein_iterations(5, 1, 25, 1, "num_tunein_iterations");
setting tunein_max_radius_deviation(1, 0, 25, 1, "tunein_max_radius_deviation");
setting cfg_none(0, 0, 0, 0, "NONE");
setting* selected_setting = &cfg_none;
bool settings_adaption()
{
int key = cv::waitKey(50);
bool modify = false;
if(key >= 0) {
switch(key & 0xffff) {
case 0xff54: --(*selected_setting); modify = true; break; // down
case 0xff52: ++(*selected_setting); modify = true; break; // up
case 0x1b: return false; // ESC
case 0x31:
selected_setting = &cfg_leveling;
cap.setup_selection(fcap_t::sis_1_leveling);
break;
case 0x32:
selected_setting = &cfg_blur;
cap.setup_selection(fcap_t::sis_2_blur);
break;
case 0x33:
selected_setting = &cfg_canny_threshold;
cap.setup_selection(fcap_t::sis_3_canny);
break;
case 0x34:
selected_setting = &cfg_num_avgerage_images;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x35:
selected_setting = &cfg_circularity;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x36:
selected_setting = &cfg_min_diameter;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x37:
selected_setting = &cfg_max_diameter;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x38:
selected_setting = &max_radius_stddev;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x39:
selected_setting = &num_tunein_iterations;
cap.setup_selection(fcap_t::sis_none);
break;
case 0x30:
selected_setting = &tunein_max_radius_deviation;
cap.setup_selection(fcap_t::sis_none);
break;
default: cerr << "keycode=" << hex << key << endl;
}
if(modify) {
if(selected_setting == &cfg_leveling) {
cap.leveling((double)cfg_leveling);
} else if(selected_setting == &cfg_blur) {
cap.blur_radius((double)cfg_blur);
} else if(selected_setting == &cfg_canny_threshold) {
cap.canny_threshold((double)cfg_canny_threshold);
} else if(selected_setting == &cfg_num_avgerage_images) {
cap.num_avgerage_images((double)cfg_num_avgerage_images);
} else if(selected_setting == &cfg_circularity) {
cap.min_circularity((double)cfg_circularity/100);
} else if(selected_setting == &cfg_min_diameter) {
cap.min_circle_diameter((double)cfg_min_diameter/100*cap.image().height());
} else if(selected_setting == &cfg_max_diameter) {
cap.max_circle_diameter((double)cfg_max_diameter/100*cap.image().height());
} else if(selected_setting == &max_radius_stddev) {
cap.max_radius_stddev((double)max_radius_stddev);
} else if(selected_setting == &num_tunein_iterations) {
cap.num_tunein_iterations((double)num_tunein_iterations);
} else if(selected_setting == &tunein_max_radius_deviation) {
cap.max_tunein_radius_deviation((double)tunein_max_radius_deviation/100);
}
}
selected_setting->disp();
}
return true;
}
int main(int argc, char** argv)
{
cap.initialize(0);
cap.draw_overlay(true);
cap.max_input_image_height(600);
cap.leveling((double)cfg_leveling);
cap.blur_radius((double)cfg_blur);
cap.canny_threshold((double)cfg_canny_threshold);
cap.num_avgerage_images((double)cfg_num_avgerage_images);
cap.min_circularity((double)cfg_circularity/100);
cap.min_circle_diameter((double)cfg_min_diameter/100*cap.image().height());
cap.max_circle_diameter((double)cfg_max_diameter/100*cap.image().height());
cap.max_radius_stddev((double)max_radius_stddev);
cap.num_tunein_iterations((double)num_tunein_iterations);
cap.max_tunein_radius_deviation((double)tunein_max_radius_deviation);
while(cap.capture()) {
cap.process();
cv::imshow("camcap", (cv::Mat&) cap.image());
if(cap.has_fiducial()) {
fcap_t::circle_t result = cap.fiducial();
cout << "[fiducial] " << result << endl;
} else {
cout << "[no-match] contours: " << cap.remaining_contours().size()
<< ", circles: " << cap.circles().size() << endl;
}
if(!settings_adaption()) break;
}
return 0;
}
Quelltext
Source code
/**
* @package de.atwillys.cc.swl.im
* @license BSD (simplified)
* @author Stefan Wilhelm (stfwi)
* @file fiducial_capture.hh
* @ccflags
* @ldflags -lopencv_core -lopencv_highgui -lopencv_imgproc
* @platform linux, bsd, (windows)
* @standard >= c++98
*
* -----------------------------------------------------------------------------
*
* Detection of circular fiducials in a camera image using OpenCV.
*
* The class allows to capture an image from a camera, process it, and to determine
* the pixel position and radius of a circular structures (full circles). This makes
* it useful to measure alignment marks for e.g. CNC routers.
*
* -----------------------------------------------------------------------------
*
* The image is preprocessed with the following sequence:
*
* 1. Reducing the image size until latter is smaller/equal a configured value set
* with max_input_image_height().
*
* 2. Conversion to gray scale
*
* 3. Leveling (reducing the colour range by making almost white pixels white,
* almost black pixels black, and linear interpolation between these min/max
* gray scale values.) Configurable using the method leveling().
*
* 4. Blurring using a Gauss filter (configuration: blur_radius( kernel size ))
*
* 5. Canny edge detection. (configuration: canny_threshold( value ) )
* (--> The cache image is now black-white.)
*
* 6. Morphological filter using diliation and erision to join pixels (only if
* `rel_morthological_filter_radius() > 0`. The filter implies removing all
* edge pixels connected to the image border.
*
* 8. Contour detection.
*
* The circle detection part is:
*
* 9. Each contour is pre-checked if it might be a circle matching the specified
* check criteria, and either added to the circle list or the remaining-contour-list.
* These checks encompass:
*
* 8.1. Bounding rectangle size check
* 8.2. Radius check derived from the contour perimeter
* 8.3. Approx circularity scoring by checking how the radius derived from the perimeter
* matches the radius derived from the contour area.
* 8.4. Point-to-radius standard deviation pre-check.
* 8.5 Algebraic circle fit returns success.
*
* +++ Please check the getters/setters for the settings +++
*
* 10. De-duplication (each canny filter edge results in two contours)
*
* 11. Number of circles are checked, exactly one circle must be matched to accept the
* result.
*
* 12. Steady filter. The maximum deviation between the last N circles must not exceed
* a maximum configured value in pixels. If the image is steady, the method
* has_fiducial() will return true, and the circle data can be fetched with the
* method fiducial().
*
* The post-processing part:
*
* 13. Optionally draw an overlay on the captured image.
*
* -----------------------------------------------------------------------------
*
* Usage example:
*
* int main(int argc, char** argv)
* {
* [ ... ]
* sw::im::fiducial_capture cap;
* cap.initialize(0);
* cap.draw_overlay(true);
* cap.max_input_image_height(0 to inf);
* cap.leveling( 0 to 0.4 );
* cap.blur_radius( 3 );
* cap.canny_threshold( 80 );
* cap.num_avgerage_images( 0 );
* cap.min_circularity( 0.9 );
* cap.min_circle_diameter( size_in_pixels );
* cap.max_circle_diameter( size_in_pixels );
* cap.max_radius_stddev( 0 or standard_deviation_in_pixels );
* cap.num_tunein_iterations( 0 to inf );
* cap.max_tunein_radius_deviation( 0 to 1 );
*
* while( !some_break_condition ) {
* if(!cap.capture()) { error ... break; }
* cap.process();
* cv::imshow("camcap", (cv::Mat&) cap.image());
*
* if(cap.has_fiducial()) {
* fcap_t::circle_t result = cap.fiducial();
* cout << result << endl;
* }
*
* [ ... ]
* }
* [ ... ]
* return 0;
* }
*
* -----------------------------------------------------------------------------
* +++ BSD license header +++
* Copyright (c) 2012-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* -----------------------------------------------------------------------------
*/
#ifndef FIDUCIAL_CAPTURE_HH
#define FIDUCIAL_CAPTURE_HH
#include <sw/vpu/cvimage.hh>
#include <sw/vpu/circlefit.hh>
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <deque>
#include <stdexcept>
namespace sw { namespace im { namespace templates {
template <typename ImgType=sw::im::cvimage>
class fiducial_capture
{
public:
typedef enum { sis_none=0, sis_1_leveling, sis_2_blur, sis_3_canny } setup_image_selection_t;
typedef circle_fit::circle_t circle_t;
public:
fiducial_capture() : cam_(), im_(), tunein_circles_(), filtered_contours_(), circles_(),
remaining_contours_(), max_input_image_height_(800), num_tunein_iterations_(0),
leveling_(0), blur_(3), rel_morthological_filter_radius_(0.01), canny_threshold_(80),
num_avgerage_images_(0), radius_stddev_(0), tunein_max_radius_deviation_(0),
circularity_(0), min_diameter_(0), max_diameter_(0), setup_selection_(sis_none),
draw_overlay_(false), fiducial_()
{}
virtual ~fiducial_capture()
{ ; }
private:
fiducial_capture(const fiducial_capture&) { ; }
void operator = (const fiducial_capture&) { ; }
public:
/**
* Initialises the object: Opens the camera channel, takes a test snapshot. Returns success.
* @param camera_capture::cam_selelct_t camera_channel
* @return bool
*/
bool initialize(camera_capture::cam_selelct_t camera_channel)
{ return cam_.open(camera_channel) && cam_.snapshot(im_); }
/**
* Captures a new image from the input device. Invalidates the currently set results.
* @return bool
*/
inline bool capture()
{
fiducial_ = circle_t(false);
circles_.clear();
remaining_contours_.clear();
return cam_.snapshot(im_);
}
/**
* Finds circles in the captured image. Draws overlay, if configured.
*/
inline void process()
{ apply_process(); }
public:
/**
* Returns the captured image as reference (if configured with overlay).
* @return cvimage&
*/
inline cvimage& image()
{ return im_; }
/**
* Returns true if one single circular fiducial was detected in the image.
* @return bool
*/
inline bool has_fiducial() const
{ return fiducial_.ok; }
/**
* Returns the detected fiducial circle. The circle has an `ok` instance variable
* to check if it is valid.
* @return const circle_t&
*/
inline const circle_t& fiducial() const
{ return fiducial_; }
/**
* Returns the circles that process() found im the captured image. That is NOT always
* only the result circle.
* @return std::vector<circle_t>&
*/
inline std::vector<circle_t>& circles()
{ return circles_; }
/**
* Returns the circles that process() found im the captured image. That is NOT always
* only the result circle.
* @return const std::vector<circle_fit::circle_t>&
*/
inline const std::vector<circle_t>& circles() const
{ return circles_; }
/**
* Returns (remaining) detected contours that did not fit the circle criteria.
* @return cvimage::contour_list_t&
*/
inline cvimage::contour_list_t& remaining_contours()
{ return remaining_contours_; }
/**
* Returns (remaining) detected contours that did not fit the circle criteria.
* @return const cvimage::contour_list_t&
*/
inline const cvimage::contour_list_t& remaining_contours() const
{ return remaining_contours_; }
public:
/**
* Returns the maximum height of the image.
* @return int
*/
inline int max_input_image_height()
{ return max_input_image_height_; }
/**
* Sets the maximum processed image height. The captured image will be resampled down
* (size /2) until the height is smaller/equal this setting.
* Note: This is applied in process() before any other operations.
* @param int h
*/
inline void max_input_image_height(int h)
{ max_input_image_height_ = h > 0 ? h : 0; }
/**
* Returns the colour leveling setting.
* @return double
*/
inline double leveling()
{ return leveling_; }
/**
* Sets the leveling, means a value between 0 and <0.5. The pixel values of the (to gray converted)
* captured image are streched between `black+v` and `white-v`, so this is to set pixels that are
* almost black to black (0) and pixels that are almost white to white (255) ; with linear color
* stretching in between. The value 0 disables leveling.
* @param double
*/
inline void leveling(double v)
{ leveling_ = v < 0 ? 0 : (v >= 0.5 ? 0.49 : v); }
/**
* Returns the (Gauss) blur radius in pixels. 0 == disabled.
* @return int
*/
inline int blur_radius()
{ return blur_; }
/**
* Sets the (Gauss) blur radius in pixels. The value 0 disables blurring.
* @param int
*/
inline void blur_radius(int v)
{ blur_ = v > 0 ? v : 0; }
/**
* Returns the lower threshold of the applied canny filter. The upper value is no configurable
* and always canny_threshold() + 20.
* @return int
*/
inline int canny_threshold()
{ return canny_threshold_; }
/**
* Sets the lower threshold of the applied canny filter. The upper value is no configurable
* and always canny_threshold() + 20.
* @param int
*/
inline void canny_threshold(int v)
{ canny_threshold_ = v < 0 ? 0 : (v > 400 ? 400 : v); }
/**
* Returns the relative "radius"/size of the applied morph filter (diliate/erode).
* The radius is a value between 0 and 1 and is set with respect to the height of the
* captured image.
* @return double
*/
inline double rel_morthological_filter_radius()
{ return rel_morthological_filter_radius_; }
/**
* Sets the relative "radius"/size of the applied morph filter (diliate/erode).
* The radius is a value between 0 and 1 and is set with respect to the height of the
* captured image. Setting the radius to 0 disables this filter, 0.01 === 1% of the height,
* etc.
* @param double
*/
inline void rel_morthological_filter_radius(double v)
{ rel_morthological_filter_radius = v > 1 ? 1 : (v < 0 ? 0 : v); }
/**
* Returns the size of the image filter buffer applied before detecting circles.
* @return int
*/
inline int num_avgerage_images()
{ return num_avgerage_images_; }
/**
* Sets the size of the image filter buffer applied before detecting circles.
* Greater numbers increase the processing time, but make the detection image more resistant
* to colour noise.
* @param int
*/
inline void num_avgerage_images(int v)
{ num_avgerage_images_ = v > 0 ? v : 0; }
/**
* Returns the minimum diameter a detected circle must have to be registered in the result.
* @return int
*/
inline int min_circle_diameter()
{ return min_diameter_; }
/**
* Sets the minimum diameter a detected circle must have to be registered in the result.
* @param int
*/
inline void min_circle_diameter(int v)
{ min_diameter_ = v > 0 ? v : 0; }
/**
* Returns the maximum diameter a detected circle can have to be registered in the result.
* @return int
*/
inline int max_circle_diameter()
{ return max_diameter_; }
/**
* Sets the maximum diameter a detected circle can have to be registered in the result.
* @param int
*/
inline void max_circle_diameter(int v)
{ max_diameter_ = v > 0 ? v : 0; }
/**
* Returns the number if process() calls that a result circle must be steady to be
* valid.
* @return int
*/
inline int num_tunein_iterations()
{ return num_tunein_iterations_; }
/**
* Sets the number if process() calls that a result circle must be steady to be
* valid. The "steady criteria" is set using max_tune-in_radius_deviation().
* If the resulting circle does not match the criteria, the tune-in resets.
* @param int
*/
inline void num_tunein_iterations(int v)
{ num_tunein_iterations_ = v > 0 ? v : 0; }
/**
* Returns the maximum geometric difference between the last N captured circles and
* the currently captured circle, where N is the number if iterations set with
* num_tunein_iterations().
* @return int
*/
inline int max_tunein_radius_deviation()
{ return tunein_max_radius_deviation_; }
/**
* Sets the maximum geometric difference between the last N captured circles and
* the currently captured circle, where N is the number if iterations set with
* num_tunein_iterations().
* @param int
*/
inline void max_tunein_radius_deviation(int v)
{ tunein_max_radius_deviation_ = v > 0 ? v : 0; }
/**
* Returns the maximum standard deviation that contour points can have to be detected as a
* potential circle result. The value is between 0 and 1 (0==don't check) and relative to
* the average radius.
* @return double
*/
inline double max_radius_stddev()
{ return radius_stddev_; }
/**
* Sets the maximum standard deviation that contour points can have to be detected as a
* potential circle result. The value is between 0 and 1 (0==don't check) and relative to
* the average radius.
* @param double
*/
inline void max_radius_stddev(double v)
{ radius_stddev_ = v > 0 ? v : 0; }
/**
* Returns the minimum circularity scoring (precheck condition) to detect a contour as a potantial
* result circle.
* @return double
*/
inline double min_circularity()
{ return circularity_; }
/**
* Returns the minimum circularity scoring (precheck condition) to detect a contour as a potantial
* result circle. This value is between 0 and 1 and should be close to 1 for expected good circles.
* The circularity score is determined by dividing the radius from the contour perimeter by the
* radius from the area. Ideally this is Ra/Rl==1. As the greater R is always divided by the
* smaller one, the result is always between 0 and 1.
*
* @param double
*/
inline void min_circularity(double v)
{ circularity_ = v > 0 ? v : 0; }
/**
* Returns the selection which setup image is returned by image(). This is useful to see
* what the filters actually do.
* @return setup_image_selection_t
*/
inline setup_image_selection_t setup_selection()
{ return setup_selection_; }
/**
* Sets the selection which setup image is returned by image(). This is useful to see
* what the filters actually do.
* @param setup_image_selection_t
*/
inline void setup_selection(setup_image_selection_t v)
{ setup_selection_ = (v >= sis_none && v <= sis_3_canny ? v : sis_none); }
/**
* If true, an overlay is drawn on the captured image returned by image()
* @return bool
*/
inline bool draw_overlay()
{ return draw_overlay_; }
/**
* If true, an overlay is drawn on the captured image returned by image()
* @param bool
*/
inline void draw_overlay(bool v)
{ draw_overlay_ = v; }
private:
/**
* Returns the circle radius standard deviation of a point list relative to the average radius.
* @param const circle_fit::points_t pts
* @return double
*/
double radius_stddev(const circle_fit::points_t pts)
{
if(pts.empty()) return false;
circle_fit::point_t centeroid;
for(circle_fit::points_t::const_iterator it = pts.begin(); it != pts.end(); ++it) {
centeroid.x += it->x; centeroid.y += it->y;
}
centeroid.x /= pts.size(); centeroid.y /= pts.size();
std::vector<double> r;
r.reserve(pts.size());
double r_agv = 0;
for(circle_fit::points_t::const_iterator it=pts.begin(); it != pts.end(); ++it) {
double x= it->x-centeroid.x;
double y= it->y-centeroid.y;
double r2 = sqrt(x*x + y*y);
r.push_back(r2);
r_agv += r2;
}
r_agv /= pts.size();
double stddev = 0;
for(std::vector<double>::iterator it = r.begin(); it != r.end(); it++) {
double d = *it - r_agv;
stddev += d * d;
}
stddev = sqrt(stddev/pts.size()) / r_agv;
return stddev;
}
/**
* Checks if a given contour might be a full circle. That is:
*
* - Bounding rectangle with/height must be nonzero
* - Diameter approximated from the perimeter must be <= max and >= min (configured)
* - Area and perimeter must fit ( M_PI*4.0 * A/(l*l) approx 1)
* - Circlefit must return OK
* - The circle must be completely in the captured image.
* - The radius standard deviation must not be greater than a configured deviation.
*
* @param onst cvimage::contour_t & contour
* @param unsigned width
* @param unsigned height
* @return circle_t
*/
circle_t circle_precheck(const cvimage::contour_t & contour, unsigned width, unsigned height)
{
circle_t circle(false);
cvimage::rect_t r = contour.bounding_rect();
if(r.height() <= 0 || r.width() <= 0) return circle;
double l = contour.perimeter();
double d = l/M_PI;
if(d < min_diameter_ || (d > max_diameter_ && max_diameter_ > 0)) {
return circle;
}
double A = contour.area();
if(A <= 0) return circle;
if(circularity_ > 0) {
double c = fabs(M_PI*4.0 * A/(l*l));
if(c > 1.0) c = 1.0/c; // ]0,1[
if(c < circularity_) return circle;
}
circle_fit::points_t pts;
pts.reserve(contour.size());
for(typename cvimage::contour_t::const_iterator it=contour.begin(); it!=contour.end(); ++it) {
pts.push_back(circle_fit::point_t(it->x(), it->y()));
}
if(radius_stddev_ > 0) {
double stddev = radius_stddev(pts);
if(stddev > radius_stddev_) return circle;
}
circle.ok = true;
circle = circle_fit::fit(pts);
circle.ok = circle.ok
&& circle.r <= height/2
&& circle.x >= circle.r
&& circle.y >= circle.r
&& circle.x <= width-circle.r
&& circle.y <= height-circle.r
&& circle.r >= min_diameter_
&& (max_diameter_ <=0 || circle.r <= max_diameter_);
return circle;
}
/**
* Detects circles from a given set of contours using precheck().
*
* @param const cvimage& im
* @param std::vector<circle_t>& circles
* @param cvimage::contour_list_t& remaining_contours
*/
void find_circles(const cvimage& im, std::vector<circle_t>& circles,
cvimage::contour_list_t& remaining_contours)
{
std::vector<circle_t> precheck_circles;
std::vector<cvimage::contour_t> precheck_contours;
cvimage::contour_list_t contours = im.contours();
unsigned height = im.height();
unsigned width = im.width();
for(typename cvimage::contour_list_t::iterator it = contours.begin();
it != contours.end(); ++it) {
cvimage::contour_t & contour = *it;
circle_t c = circle_precheck(contour, width, height);
if(!c.ok) {
remaining_contours.push_back(cvimage::contour_t());
remaining_contours.back().swap(contour);
} else {
precheck_circles.push_back(c);
precheck_contours.push_back(cvimage::contour_t());
precheck_contours.back().swap(contour);
}
}
// Collect contours of the same circles (only very few < 20, normally 2 to 6),
// so copying container data a bit is ok here.
if(!precheck_circles.empty()) {
double max_diff = (double) height / 100;
max_diff = max_diff < 10 ? 10 : max_diff;
std::vector<circle_fit::points_t> pts(precheck_circles.size());
for(unsigned i=0; i<precheck_circles.size(); ++i) {
circle_t& c0 = precheck_circles[i];
if(!c0.ok) continue;
c0.ok = false;
cvimage::contour_t& cont0 = precheck_contours[i];
circle_fit::points_t& cpts = pts[i];
cpts.reserve(cont0.size());
for(typename cvimage::contour_t::iterator it = cont0.begin(); it != cont0.end(); ++it) {
cpts.push_back(circle_fit::point_t(it->x(), it->y()));
}
for(unsigned j=i+1; j<precheck_circles.size(); ++j) {
circle_t &c1 = precheck_circles[j];
cvimage::contour_t& cont1 = precheck_contours[j];
if(!c1.ok) continue;
if((fabs(c0.r-c1.r) < max_diff) && (fabs(c0.x-c1.x) < max_diff)
&& (fabs(c0.y-c1.y) < max_diff)) {
c1.ok = false;
cpts.reserve(cont0.size() + cont1.size());
for(typename cvimage::contour_t::iterator it = cont1.begin(); it != cont1.end(); ++it) {
cpts.push_back(circle_fit::point_t(it->x(), it->y()));
}
}
}
}
// The accurate fit with points of both inner and outer contour.
for(std::vector<circle_fit::points_t>::iterator it = pts.begin(); it != pts.end(); ++it) {
if(it->empty()) continue;
circle_t circle = circle_fit::fit(*it);
if(circle.ok) circles.push_back(circle);
}
}
}
/**
* Draws an overlay on the captured image.
*/
void apply_overlay()
{
im_.to_rgb();
if(circles_.size() == 1) {
circle_t c = circles_.front();
if(has_fiducial()) {
im_.draw(cvimage::circle_t(c.x, c.y, c.r), "00ff00", 1);
im_.draw(cvimage::point_t(c.x, c.y), "00ff00", 2);
} else {
im_.draw(cvimage::circle_t(c.x, c.y, c.r), "ffff00", 1);
}
} else {
for(typename cvimage::contour_list_t::iterator it = remaining_contours_.begin();
it != remaining_contours_.end(); ++it) {
im_.draw(*it, "ff00ff", 1);
}
for(typename std::vector<circle_t>::iterator it = circles_.begin(); it != circles_.end(); ++it) {
im_.draw(cvimage::circle_t(it->x, it->y, it->r), "ff0000", 1);
}
}
}
/**
* Processes the captured image.
*/
void apply_process()
{
if(max_input_image_height_) {
while(im_.height() > max_input_image_height_) im_.resample_down();
}
// Processing image (gray)
cvimage im_edges = im_;
{
im_edges.to_grayscale();
if(leveling_ > 0 && leveling_ < 0.5) im_edges.level(leveling_,1.0-leveling_);
if(draw_overlay_ && setup_selection_ == sis_1_leveling) im_ = im_edges;
if(blur_ > 1) im_edges.blur(blur_);
if(draw_overlay_ && setup_selection_ == sis_2_blur) im_ = im_edges;
im_edges.edges_canny(canny_threshold_, canny_threshold_+20);
if(draw_overlay_ && setup_selection_ == sis_3_canny) im_ = im_edges;
// Morphological filtering
if(rel_morthological_filter_radius_ > 0) {
cvimage morth_mask = im_edges;
int op_radius = rel_morthological_filter_radius_ * morth_mask.height();
op_radius = op_radius <= 0 ? 1 : op_radius;
morth_mask
.dilate(op_radius) // sw: implement basic_image::morth_filter for multiple dilte-erde
.erode(op_radius)
.dilate(op_radius)
.draw(cvimage::rect_t(cvimage::point_t(0,0), cvimage::point_t(im_edges.width(),
im_edges.height())), color::white, 1)
.floodfill(cvimage::point_t(0,0), color::black, 50, true)
.erode(op_radius)
;
im_edges.mask(morth_mask);
}
// Noise filter image average
if(num_avgerage_images_ > 0) {
while((int)filtered_contours_.size() >= num_avgerage_images_) filtered_contours_.pop_front();
im_edges.mat() /= num_avgerage_images_;
filtered_contours_.push_back(im_edges);
if((int)filtered_contours_.size() == num_avgerage_images_) {
im_edges.mat() = 0.0;
for(typename std::deque<cvimage>::const_iterator it = filtered_contours_.begin();
it != filtered_contours_.end(); ++it) {
im_edges.mat() += it->mat();
}
}
im_edges.threshold(0.2);
}
}
// Circle filter
find_circles(im_edges, circles_, remaining_contours_);
// Tune-in
if(circles_.size() != 1 || !circles_.front().ok) {
tunein_circles_.clear();
fiducial_.ok = false; // ensure this is set.
} else {
circle_t c = circles_.front();
tunein_circles_.push_back(c);
while(!tunein_circles_.empty() && (int)tunein_circles_.size() > num_tunein_iterations_) {
tunein_circles_.pop_front();
}
if((int)tunein_circles_.size() < num_tunein_iterations_) {
fiducial_.ok = false;
} else {
double maxx, maxy, minx, miny, xx, yy, rr;
maxx = minx = xx = tunein_circles_.front().x;
maxy = miny = yy = tunein_circles_.front().y;
rr = tunein_circles_.front().r;
for(std::deque<circle_t>::const_iterator it=tunein_circles_.begin();
it != tunein_circles_.end(); ++it) {
if(it->x > maxx) maxx = it->x; else if(it->x < minx) minx = it->x;
if(it->y > maxy) maxy = it->y; else if(it->y < miny) miny = it->y;
xx = 0.5 * xx + 0.5 * it->x; // s-T1-like filter
yy = 0.5 * yy + 0.5 * it->y;
rr = 0.5 * rr + 0.5 * it->r;
}
maxx -= minx;
maxy -= miny;
if(maxx*maxx + maxy*maxy <= tunein_max_radius_deviation_) {
fiducial_ = circle_t(xx,yy,rr);
} else {
fiducial_.ok = false;
}
}
}
// Image output
if(draw_overlay_) {
apply_overlay();
}
}
private:
camera_capture cam_;
cvimage im_;
std::deque<circle_t> tunein_circles_;
std::deque<cvimage> filtered_contours_;
std::vector<circle_t> circles_;
cvimage::contour_list_t remaining_contours_;
int max_input_image_height_;
int num_tunein_iterations_;
double leveling_;
int blur_;
double rel_morthological_filter_radius_;
int canny_threshold_;
int num_avgerage_images_;
double radius_stddev_;
int tunein_max_radius_deviation_;
double circularity_;
int min_diameter_;
int max_diameter_;
setup_image_selection_t setup_selection_;
bool draw_overlay_;
circle_t fiducial_;
};
} // ns templates
/**
* Default specialisation
*/
typedef templates::fiducial_capture<> fiducial_capture;
}}
#endif