Commit 8811dabb authored by Alexander Duda's avatar Alexander Duda Committed by Alexander Alekhin

Merge pull request #12615 from D-Alex:master

findChessboardCornersSB: speed improvements (#12615)

* chessboard: fix do not modify const image

* chessboard: speed up scale space using parallel_for

* chessboard: small improvements

* chessboard: speed up board growing using parallel_for

* chessboard: add flags for tuning detection

* chessboard: fix compiler warnings

* chessborad: change flag name to CALIB_CB_EXHAUSTIVE

This also fixes a typo

* chessboard: fix const ref + remove to_string
parent efca746d
...@@ -244,7 +244,9 @@ enum { SOLVEPNP_ITERATIVE = 0, ...@@ -244,7 +244,9 @@ enum { SOLVEPNP_ITERATIVE = 0,
enum { CALIB_CB_ADAPTIVE_THRESH = 1, enum { CALIB_CB_ADAPTIVE_THRESH = 1,
CALIB_CB_NORMALIZE_IMAGE = 2, CALIB_CB_NORMALIZE_IMAGE = 2,
CALIB_CB_FILTER_QUADS = 4, CALIB_CB_FILTER_QUADS = 4,
CALIB_CB_FAST_CHECK = 8 CALIB_CB_FAST_CHECK = 8,
CALIB_CB_EXHAUSTIVE = 16,
CALIB_CB_ACCURACY = 32
}; };
enum { CALIB_CB_SYMMETRIC_GRID = 1, enum { CALIB_CB_SYMMETRIC_GRID = 1,
...@@ -847,7 +849,11 @@ CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, Out ...@@ -847,7 +849,11 @@ CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, Out
@param patternSize Number of inner corners per a chessboard row and column @param patternSize Number of inner corners per a chessboard row and column
( patternSize = cv::Size(points_per_row,points_per_colum) = cv::Size(columns,rows) ). ( patternSize = cv::Size(points_per_row,points_per_colum) = cv::Size(columns,rows) ).
@param corners Output array of detected corners. @param corners Output array of detected corners.
@param flags operation flags for future improvements @param flags Various operation flags that can be zero or a combination of the following values:
- **CALIB_CB_NORMALIZE_IMAGE** Normalize the image gamma with equalizeHist before detection.
- **CALIB_CB_EXHAUSTIVE ** Run an exhaustive search to improve detection rate.
- **CALIB_CB_ACCURACY ** Up sample input image to improve sub-pixel accuracy due to aliasing effects.
This should be used if an accurate camera calibration is required.
The function is analog to findchessboardCorners but uses a localized radon The function is analog to findchessboardCorners but uses a localized radon
transformation approximated by box filters being more robust to all sort of transformation approximated by box filters being more robust to all sort of
......
...@@ -24,7 +24,7 @@ namespace details { ...@@ -24,7 +24,7 @@ namespace details {
const float CORNERS_SEARCH = 0.5F; // percentage of the edge length to the next corner used to find new corners const float CORNERS_SEARCH = 0.5F; // percentage of the edge length to the next corner used to find new corners
const float MAX_ANGLE = float(48.0/180.0*M_PI); // max angle between line segments supposed to be straight const float MAX_ANGLE = float(48.0/180.0*M_PI); // max angle between line segments supposed to be straight
const float MIN_COS_ANGLE = float(cos(35.0/180*M_PI)); // min cos angle between board edges const float MIN_COS_ANGLE = float(cos(35.0/180*M_PI)); // min cos angle between board edges
const float MIN_RESPONSE_RATIO = 0.3F; const float MIN_RESPONSE_RATIO = 0.1F;
const float ELLIPSE_WIDTH = 0.35F; // width of the search ellipse in percentage of its length const float ELLIPSE_WIDTH = 0.35F; // width of the search ellipse in percentage of its length
const float RAD2DEG = float(180.0/M_PI); const float RAD2DEG = float(180.0/M_PI);
const int MAX_SYMMETRY_ERRORS = 5; // maximal number of failures during point symmetry test (filtering out lines) const int MAX_SYMMETRY_ERRORS = 5; // maximal number of failures during point symmetry test (filtering out lines)
...@@ -602,63 +602,67 @@ void FastX::detectAndCompute(cv::InputArray image,cv::InputArray mask,std::vecto ...@@ -602,63 +602,67 @@ void FastX::detectAndCompute(cv::InputArray image,cv::InputArray mask,std::vecto
return; return;
} }
void FastX::detectImpl(const cv::Mat& gray_image, void FastX::detectImpl(const cv::Mat& _gray_image,
std::vector<cv::Mat> &rotated_images, std::vector<cv::Mat> &rotated_images,
std::vector<cv::Mat> &feature_maps, std::vector<cv::Mat> &feature_maps,
const cv::Mat &_mask)const const cv::Mat &_mask)const
{ {
if(!_mask.empty()) if(!_mask.empty())
CV_Error(Error::StsBadSize, "Mask is not supported"); CV_Error(Error::StsBadSize, "Mask is not supported");
CV_CheckTypeEQ(gray_image.type(), CV_8UC1, "Unsupported image type"); CV_CheckTypeEQ(_gray_image.type(), CV_8UC1, "Unsupported image type");
// up-sample if needed // up-sample if needed
cv::Mat gray_image;
int super_res = int(parameters.super_resolution); int super_res = int(parameters.super_resolution);
if(super_res) if(super_res)
cv::resize(gray_image,gray_image,cv::Size(),2,2); cv::resize(_gray_image,gray_image,cv::Size(),2,2);
else
gray_image = _gray_image;
//for each scale //for each scale
int num_scales = parameters.max_scale-parameters.min_scale+1; int num_scales = parameters.max_scale-parameters.min_scale+1;
rotated_images.resize(num_scales); rotated_images.resize(num_scales);
feature_maps.resize(num_scales); feature_maps.resize(num_scales);
for(int scale=parameters.min_scale;scale <= parameters.max_scale;++scale) parallel_for_(Range(parameters.min_scale,parameters.max_scale+1),[&](const Range& range){
{ for(int scale=range.start;scale < range.end;++scale)
// calc images {
// for each angle step // calc images
int scale_id = scale-parameters.min_scale; // for each angle step
cv::Mat rotated,filtered_h,filtered_v; int scale_id = scale-parameters.min_scale;
int diag = int(sqrt(gray_image.rows*gray_image.rows+gray_image.cols*gray_image.cols)); cv::Mat rotated,filtered_h,filtered_v;
cv::Size size(diag,diag); int diag = int(sqrt(gray_image.rows*gray_image.rows+gray_image.cols*gray_image.cols));
int num = int(0.5001*M_PI/parameters.resolution); cv::Size size(diag,diag);
std::vector<cv::Mat> images; int num = int(0.5001*M_PI/parameters.resolution);
images.resize(2*num); std::vector<cv::Mat> images;
int scale_size = int(1+pow(2.0,scale+1+super_res)); images.resize(2*num);
int scale_size2 = int((scale_size/10)*2+1); int scale_size = int(1+pow(2.0,scale+1+super_res));
for(int i=0;i<num;++i) int scale_size2 = int((scale_size/10)*2+1);
{ for(int i=0;i<num;++i)
float angle = parameters.resolution*i; {
rotate(-angle,gray_image,size,rotated); float angle = parameters.resolution*i;
cv::blur(rotated,filtered_h,cv::Size(scale_size,scale_size2)); rotate(-angle,gray_image,size,rotated);
cv::blur(rotated,filtered_v,cv::Size(scale_size2,scale_size)); cv::blur(rotated,filtered_h,cv::Size(scale_size,scale_size2));
cv::blur(rotated,filtered_v,cv::Size(scale_size2,scale_size));
// rotate filtered images back
rotate(angle,filtered_h,gray_image.size(),images[i]); // rotate filtered images back
rotate(angle,filtered_v,gray_image.size(),images[i+num]); rotate(angle,filtered_h,gray_image.size(),images[i]);
} rotate(angle,filtered_v,gray_image.size(),images[i+num]);
cv::merge(images,rotated_images[scale_id]); }
cv::merge(images,rotated_images[scale_id]);
// calc feature map
calcFeatureMap(rotated_images[scale_id],feature_maps[scale_id]);
// filter feature map to improve impulse responses // calc feature map
if(parameters.filter) calcFeatureMap(rotated_images[scale_id],feature_maps[scale_id]);
{ // filter feature map to improve impulse responses
cv::Mat high,low; if(parameters.filter)
cv::blur(feature_maps[scale_id],low,cv::Size(scale_size,scale_size)); {
int scale2 = int((scale_size/6))*2+1; cv::Mat high,low;
cv::blur(feature_maps[scale_id],high,cv::Size(scale2,scale2)); cv::blur(feature_maps[scale_id],low,cv::Size(scale_size,scale_size));
feature_maps[scale_id] = high-0.8*low; int scale2 = int((scale_size/6))*2+1;
cv::blur(feature_maps[scale_id],high,cv::Size(scale2,scale2));
feature_maps[scale_id] = high-0.8*low;
}
} }
} });
} }
void FastX::detectImpl(const cv::Mat& image,std::vector<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,const cv::Mat &mask)const void FastX::detectImpl(const cv::Mat& image,std::vector<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,const cv::Mat &mask)const
...@@ -1865,6 +1869,7 @@ cv::Point2f &Chessboard::Board::getCorner(int _row,int _col) ...@@ -1865,6 +1869,7 @@ cv::Point2f &Chessboard::Board::getCorner(int _row,int _col)
}while(_row); }while(_row);
} }
CV_Error(Error::StsInternal,"cannot find corner"); CV_Error(Error::StsInternal,"cannot find corner");
// return *top_left->top_left; // never reached
} }
bool Chessboard::Board::isCellBlack(int row,int col)const bool Chessboard::Board::isCellBlack(int row,int col)const
...@@ -3008,11 +3013,6 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f ...@@ -3008,11 +3013,6 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f
#endif #endif
CV_CheckTypeEQ(gray.type(),CV_8UC1, "Unsupported image type"); CV_CheckTypeEQ(gray.type(),CV_8UC1, "Unsupported image type");
//TODO is this needed?
// double min,max;
// cv::minMaxLoc(gray,&min,&max);
// gray = (gray-min)*(255.0/(max-min));
cv::Size chessboard_size2(parameters.chessboard_size.height,parameters.chessboard_size.width); cv::Size chessboard_size2(parameters.chessboard_size.height,parameters.chessboard_size.width);
std::vector<KeyPoint> keypoints_seed; std::vector<KeyPoint> keypoints_seed;
std::vector<std::vector<float> > angles; std::vector<std::vector<float> > angles;
...@@ -3025,17 +3025,15 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f ...@@ -3025,17 +3025,15 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f
std::vector<KeyPoint>::const_iterator seed_iter = keypoints_seed.begin(); std::vector<KeyPoint>::const_iterator seed_iter = keypoints_seed.begin();
int count = 0; int count = 0;
int inum = chessboard_size2.width*chessboard_size2.height; int inum = chessboard_size2.width*chessboard_size2.height;
for(;seed_iter != keypoints_seed.end();++seed_iter) for(;seed_iter != keypoints_seed.end() && count < inum;++seed_iter,++count)
{ {
if(fabs(seed_iter->response) > response) // points are sorted based on response
if(fabs(seed_iter->response) < response)
{ {
++count; seed_iter = keypoints_seed.end();
if(count >= inum) return Chessboard::Board();
break;
} }
} }
if(seed_iter == keypoints_seed.end())
return Chessboard::Board();
// just add dummy points or flann will fail during knnSearch // just add dummy points or flann will fail during knnSearch
if(keypoints_seed.size() < 21) if(keypoints_seed.size() < 21)
keypoints_seed.resize(21, cv::KeyPoint(-99999.0F,-99999.0F,0.0F,0.0F,0.0F)); keypoints_seed.resize(21, cv::KeyPoint(-99999.0F,-99999.0F,0.0F,0.0F,0.0F));
...@@ -3070,60 +3068,71 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f ...@@ -3070,60 +3068,71 @@ Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &f
std::vector<Board> boards; std::vector<Board> boards;
generateBoards(flann_index, data,*points_iter,white_angle,black_angle,min_response,gray,boards); generateBoards(flann_index, data,*points_iter,white_angle,black_angle,min_response,gray,boards);
std::vector<Chessboard::Board>::iterator iter_boards = boards.begin(); parallel_for_(Range(0,(int)boards.size()),[&](const Range& range){
for(;iter_boards != boards.end();++iter_boards) for(int i=range.start;i <range.end;++i)
{
cv::Mat h = iter_boards->estimateHomography();
int size = iter_boards->validateCorners(data,flann_index,h,min_response);
if(size != 9)
continue;
if(!iter_boards->validateContour())
continue;
//grow based on kd-tree
iter_boards->grow(data,flann_index);
if(!iter_boards->checkUnique())
continue;
// check bounding box
std::vector<cv::Point2f> contour = iter_boards->getContour();
std::vector<cv::Point2f>::const_iterator iter = contour.begin();
for(;iter != contour.end();++iter)
{ {
if(!bounding_box.contains(*iter)) auto iter_boards = boards.begin()+i;
break; cv::Mat h = iter_boards->estimateHomography();
} int size = iter_boards->validateCorners(data,flann_index,h,min_response);
if(iter != contour.end()) if(size != 9 || !iter_boards->validateContour())
continue; {
iter_boards->clear();
continue;
}
//grow based on kd-tree
iter_boards->grow(data,flann_index);
if(!iter_boards->checkUnique())
{
iter_boards->clear();
continue;
}
if(iter_boards->getSize() == parameters.chessboard_size || // check bounding box
iter_boards->getSize() == chessboard_size2) std::vector<cv::Point2f> contour = iter_boards->getContour();
{ std::vector<cv::Point2f>::const_iterator iter = contour.begin();
iter_boards->normalizeOrientation(false); for(;iter != contour.end();++iter)
if(iter_boards->getSize() != parameters.chessboard_size) {
if(!bounding_box.contains(*iter))
break;
}
if(iter != contour.end())
{ {
if(iter_boards->isCellBlack(0,0) == iter_boards->isCellBlack(0,int(iter_boards->colCount())-1)) iter_boards->clear();
iter_boards->rotateLeft(); continue;
else
iter_boards->rotateRight();
} }
if(iter_boards->getSize() == parameters.chessboard_size ||
iter_boards->getSize() == chessboard_size2)
{
iter_boards->normalizeOrientation(false);
if(iter_boards->getSize() != parameters.chessboard_size)
{
if(iter_boards->isCellBlack(0,0) == iter_boards->isCellBlack(0,int(iter_boards->colCount())-1))
iter_boards->rotateLeft();
else
iter_boards->rotateRight();
}
#ifdef CV_DETECTORS_CHESSBOARD_DEBUG #ifdef CV_DETECTORS_CHESSBOARD_DEBUG
cv::Mat img; cv::Mat img;
iter_boards->draw(debug_image,img); iter_boards->draw(debug_image,img);
cv::imshow("chessboard",img); cv::imshow("chessboard",img);
cv::waitKey(-1); cv::waitKey(-1);
#endif #endif
return *iter_boards; }
} else
else
{
if(iter_boards->getSize().width*iter_boards->getSize().height > chessboard_size2.width*chessboard_size2.height)
{ {
if(parameters.larger) if(iter_boards->getSize().width*iter_boards->getSize().height < chessboard_size2.width*chessboard_size2.height)
return *iter_boards; iter_boards->clear();
else else if(!parameters.larger)
return Chessboard::Board(); iter_boards->clear();
} }
} }
});
// check if a good board was found
for(const auto &board : boards)
{
if(!board.isEmpty())
return board;
} }
} }
return Chessboard::Board(); return Chessboard::Board();
...@@ -3175,24 +3184,32 @@ bool cv::findChessboardCornersSB(cv::InputArray image_, cv::Size pattern_size, ...@@ -3175,24 +3184,32 @@ bool cv::findChessboardCornersSB(cv::InputArray image_, cv::Size pattern_size,
details::Chessboard::Parameters para; details::Chessboard::Parameters para;
para.chessboard_size = pattern_size; para.chessboard_size = pattern_size;
para.min_scale = 2;
para.max_scale = 4;
para.max_tests = 25;
para.max_points = std::max(100,pattern_size.width*pattern_size.height*2);
para.super_resolution = false;
switch(flags) // setup search based on flags
if(flags & CALIB_CB_NORMALIZE_IMAGE)
{
cv::equalizeHist(img,img);
flags ^= CALIB_CB_NORMALIZE_IMAGE;
}
if(flags & CALIB_CB_EXHAUSTIVE)
{ {
case 1: // high accuracy profile
para.min_scale = 2;
para.max_scale = 4;
para.max_tests = 100; para.max_tests = 100;
para.super_resolution = true;
para.max_points = std::max(500,pattern_size.width*pattern_size.height*2); para.max_points = std::max(500,pattern_size.width*pattern_size.height*2);
break; flags ^= CALIB_CB_EXHAUSTIVE;
default: // default profile }
para.min_scale = 2; if(flags & CALIB_CB_ACCURACY)
para.max_scale = 3; {
para.max_tests = 20; para.super_resolution = true;
para.max_points = pattern_size.width*pattern_size.height*2; flags ^= CALIB_CB_ACCURACY;
para.super_resolution = false;
break;
} }
if(flags)
CV_Error(Error::StsOutOfRange, cv::format("Invalid remaing flags %d", (int)flags));
std::vector<cv::KeyPoint> corners; std::vector<cv::KeyPoint> corners;
details::Chessboard board(para); details::Chessboard board(para);
board.detect(img,corners); board.detect(img,corners);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment