-
-
Notifications
You must be signed in to change notification settings - Fork 56.5k
HoughCircles accuracy decreased in opencv 4.1.1 after upgrade from 3.1.0 #16187
Description
System information (version)
- OpenCV => 4.1.1
- Operating System / Platform => Windows 64 Bit
- Compiler =>Visual Studio 2019
Detailed description
The HoughCircles() from version 3.1.0 was used to detect the circles in 8bit gray value images with a resolution of 320x240 pixel. After an upgrade to version 4.1.1 the HoughCircles() detection accuraccy - the euclidean distance between ground truth (HoughCircles 3.1.0) and a detected centre decreased of 10% of an image resolution. Similar accuraccy loss has been observed by the radius values.

Circle position error in pixels tested on 438 images

Radius values error in pixels tested on 438 images

Histogram with frequencies of error classes.
Steps to reproduce
- A sample with HoughCircles call has been compiled with an opencv 3.1.0 build from source code, the results and parameters have been saved as metadata.
- A sample with HoughCircles call has been compiled with an opencv 4.1.1 build from source code, the parameters and results from 3.1.0 have been read. The difference has been saved as metadata.
- Results have been visualized.
Sample source code
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <string>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <list>
using namespace cv;
using namespace std;
namespace fs = std::filesystem;
bool read_params(const fs::path& prm_file_path, int& rnX, int& rnY, int& rnW, int& rnH, double& rdMinDist, double& rdCanny, double& rdNumVotes, int& rnMinRad, int& rnMaxRad, int& rnNumCircles, double& rdCentX, double& rdCentY, double& rdRadius)
{
if (!fs::exists(prm_file_path))
{
return false;
}
ifstream oStream;
oStream.open(prm_file_path.string());
if (!oStream.is_open())
{
return false;
}
int nTmp;
oStream >> nTmp; // cnScale
oStream >> rnX; // X
oStream >> rnY; // Y
oStream >> rnW; // W
oStream >> rnH; // H
oStream >> rdMinDist; // min dist
oStream >> rdCanny; // canny thresh
oStream >> rdNumVotes; // num votes
oStream >> rnMinRad; // min radius
oStream >> rnMaxRad; // max radius
oStream >> rnNumCircles; // number of circles found
if (0 != rnNumCircles)
{
oStream >> rdCentX; // first circle center X
oStream >> rdCentY; // first circle center Y
oStream >> rdRadius; // first circle radius
}
else
{
rdCentX = 0.0;
rdCentY = 0.0;
rdRadius = 0.0;
}
oStream.close();
return true;
}
bool find_hough_circles(const cv::Mat& input_img, const int& nX, const int& nY, const int& nW, const int& nH, const double& dMinDist, const double& dCanny, const double& dNumVotes, const int& nMinRad, const int& nMaxRad, int& rnNumCircles, double& rdCentX, double& rdCentY, double& rdRadius)
{
Rect roi = Rect(nX, nY, nW, nH);
Mat roi_image = input_img(roi);
vector<Vec3f> aCircles;
try
{
HoughCircles(roi_image, aCircles, cv::HOUGH_GRADIENT, 2.0, dMinDist, dCanny, dNumVotes, nMinRad, nMaxRad);
}
catch (const cv::Exception & e)
{
std::cout << "Exception thrown: " << e.what() << std::endl;
return false;
}
rnNumCircles = (int)aCircles.size();
if (rnNumCircles)
{
rdCentX = aCircles[0][0];
rdCentY = aCircles[0][1];
rdRadius = aCircles[0][2];
}
else
{
rdCentX = 0.0;
rdCentY = 0.0;
rdRadius = 0.0;
}
return true;
}
fs::path find_dbg_filename(const list<fs::path>& dbg_names, const string& dbg_subname)
{
for (const auto& entry : dbg_names)
{
const fs::path& entry_stem = entry.stem();
if (entry_stem.string() == dbg_subname)
{
return entry;
}
}
return fs::path();
}
int main(int argc, char* argv[])
{
std::cout << "Starting test with opencv 4.1.1\n";
if (3 == argc)
{
if (0 == strcmp("-d", argv[1]))
{
std::cout << "Starting the test in directory: " << argv[2] << std::endl;
string dir_path(argv[2]);
if (!fs::exists(dir_path) ||
!fs::is_directory(dir_path))
{
std::cout << "Source directory doesn't exist or given path is not a directory. The test will terminate..." << std::endl;
return -1;
}
if (0 == strcmp(&dir_path.back(), "/") ||
0 == strcmp(&dir_path.back(), "\\"))
{
dir_path.pop_back();
}
ofstream result_file;
fs::path src_dir_path(dir_path);
fs::path result_file_path(fs::current_path());
fs::path result_file_stem = src_dir_path.stem();
result_file_path += "\\results_";
result_file_path += result_file_stem;
result_file_path += "_411.txt";
//result_file.open(dir_path + "\\" + "results.txt", ofstream::out | ofstream::trunc);
result_file.open(result_file_path, ofstream::out | ofstream::trunc);
if (!result_file.is_open())
{
cout << "Can't open result file!" << endl;
return -1;
}
result_file << "Image name" << "\t" << "Num circles found (difference)" << "\t" << "first found circle X (difference)" << "\t"
<< "first found circle Y (difference)" << "\t" << "first found circle R (difference)" << "\t" << "Num circles found (orig)" << "\t" << "first found circle X (orig)" << "\t"
<< "first found circle Y (orig)" << "\t" << "first found circle R (orig)" << "\t" << "Num circles found" << "\t" << "first found circle X" << "\t" << "first found circle Y" << "\t"
<< "first found circle R" << "\t" << "ROI X" << "\t" << "ROI Y" << "\t" << "ROI W" << "\t" << "ROI H" << "\t" << "HC Param min dist" << "\t"
<< "HC Param Canny Threashold" << "\t" << "HC Param Min Votes" << "\t" << "HC Param min r" << "\t" << "HC Param max r" << endl;
list <fs::path> txt_entries;
list <fs::path> img_in_entries;
list <fs::path> img_dbg_entries;
for (const auto& entry : fs::directory_iterator(dir_path))
{
string extension = entry.path().extension().string();
if (entry.is_regular_file())
{
if (".txt" == entry.path().extension() &&
"results" != entry.path().stem())
{
txt_entries.push_back(entry);
}
else if (".png" == extension ||
".jpg" == extension ||
".bmp" == extension)
{
if (string::npos != entry.path().string().find("_dbg"))
{
img_dbg_entries.push_back(entry);
}
else
{
img_in_entries.push_back(entry);
}
}
else
{
// nothing to do here (unknown file)
}
}
}
//search the circle
for (const auto& txt_entry : txt_entries)
{
// test
double dDist, dCanny, dVotes, dCx, dCy, dR, dCxOrig, dCyOrig, dROrig;
int nX, nY, nW, nH, nMin, nMax, nCount, nCountOrig;
if (read_params(txt_entry, nX, nY, nW, nH, dDist, dCanny, dVotes, nMin, nMax, nCountOrig, dCxOrig, dCyOrig, dROrig))
{
string dbg_img_path_name = txt_entry.stem().string();
dbg_img_path_name += "_dbg";
fs::path dbg_img_path = find_dbg_filename(img_dbg_entries, dbg_img_path_name);
Mat input_img;
input_img = imread(dbg_img_path.string(), 0);
if (input_img.data)
{
flip(input_img, input_img, 0);
if (find_hough_circles(input_img, nX, nY, nW, nH, dDist, dCanny, dVotes, nMin, nMax, nCount, dCx, dCy, dR))
{
int nDCount = abs(nCountOrig - nCount);
float dDX = abs(dCxOrig - dCx);
float dDY = abs(dCyOrig - dCy);
float dDR = abs(dROrig - dR);
result_file << txt_entry.stem().string() << "\t" << nDCount << "\t" << std::fixed << dDX << "\t" << dDY << "\t" << dDR << "\t" << nCountOrig << "\t" << dCxOrig << "\t"
<< dCyOrig << "\t" << dROrig << "\t" << nCount << "\t" << dCx << "\t" << dCy << "\t"
<< dR << "\t" << nX << "\t" << nY << "\t" << nW << "\t" << nH << "\t" << dDist << "\t"
<< dCanny << "\t" << dVotes << "\t" << nMin << "\t" << nMax << endl;
if (nCount)
{
try
{
Mat out_img;
cvtColor(input_img, out_img, cv::COLOR_GRAY2RGB);
Rect roi = Rect(nX, nY, nW, nH);
Mat roi_image = out_img(roi);
circle(roi_image, cv::Point(dCx, dCy), (int)dR, cv::Scalar(255, 255, 0));
fs::path out_img_path(src_dir_path);
out_img_path += "\\";
out_img_path += txt_entry.stem().string();
out_img_path += "_out_411.png";
imwrite(out_img_path.string(), roi_image);
}
catch (const cv::Exception & e)
{
cout << e.what() << endl;
}
}
}
else
{
cout << "Error occured while searchign hough circles in: " << dbg_img_path_name << endl;
result_file << "Error occured while searchign hough circles in: " << dbg_img_path_name << endl;
}
}
else
{
cout << "Can't read image: " << dbg_img_path_name << endl;
result_file << "Can't read image: " << dbg_img_path_name << endl;
}
}
else
{
cout << "Can't read parameter file: " << txt_entry << endl;
result_file << "Can't read parameter file: " << txt_entry << endl;
}
}
std::cout << "Results written to: " << result_file_path << std::endl;
}
else
{
std::cout << "Wrong number of parameters, test will terminate..." << std::endl;
return -1;
}
}
else
{
std::cout << "Wrong number of parameters, test will terminate..." << std::endl;
return -1;
}
return 0;
}
Following test data show the input images (_dbg) results of the 3.1.0 (magenta circles) and results of the 4.1.1 (cyan circles) The tendence of the algorithm to provide mostly large circles can be in the 4.1.1 suppressed by the normalisation (votes/diameter) as the new algorithm provides also the votes of the found circles. This leads to not much better results. The text files contain the parameters which have been used during circles matching. The data have to be put in the directory and path to the directory has to be used as a parameter in the command line.














