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
This commit is contained in:
Alexander Duda 2018-09-25 16:06:46 +02:00 committed by Alexander Alekhin
parent efca746d36
commit 8811dabbac
2 changed files with 138 additions and 115 deletions

View File

@ -244,7 +244,9 @@ enum { SOLVEPNP_ITERATIVE = 0,
enum { CALIB_CB_ADAPTIVE_THRESH = 1,
CALIB_CB_NORMALIZE_IMAGE = 2,
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,
@ -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
( patternSize = cv::Size(points_per_row,points_per_colum) = cv::Size(columns,rows) ).
@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
transformation approximated by box filters being more robust to all sort of

View File

@ -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 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_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 RAD2DEG = float(180.0/M_PI);
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
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> &feature_maps,
const cv::Mat &_mask)const
{
if(!_mask.empty())
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
cv::Mat gray_image;
int super_res = int(parameters.super_resolution);
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
int num_scales = parameters.max_scale-parameters.min_scale+1;
rotated_images.resize(num_scales);
feature_maps.resize(num_scales);
for(int scale=parameters.min_scale;scale <= parameters.max_scale;++scale)
{
// calc images
// for each angle step
int scale_id = scale-parameters.min_scale;
cv::Mat rotated,filtered_h,filtered_v;
int diag = int(sqrt(gray_image.rows*gray_image.rows+gray_image.cols*gray_image.cols));
cv::Size size(diag,diag);
int num = int(0.5001*M_PI/parameters.resolution);
std::vector<cv::Mat> images;
images.resize(2*num);
int scale_size = int(1+pow(2.0,scale+1+super_res));
int scale_size2 = int((scale_size/10)*2+1);
for(int i=0;i<num;++i)
parallel_for_(Range(parameters.min_scale,parameters.max_scale+1),[&](const Range& range){
for(int scale=range.start;scale < range.end;++scale)
{
float angle = parameters.resolution*i;
rotate(-angle,gray_image,size,rotated);
cv::blur(rotated,filtered_h,cv::Size(scale_size,scale_size2));
cv::blur(rotated,filtered_v,cv::Size(scale_size2,scale_size));
// calc images
// for each angle step
int scale_id = scale-parameters.min_scale;
cv::Mat rotated,filtered_h,filtered_v;
int diag = int(sqrt(gray_image.rows*gray_image.rows+gray_image.cols*gray_image.cols));
cv::Size size(diag,diag);
int num = int(0.5001*M_PI/parameters.resolution);
std::vector<cv::Mat> images;
images.resize(2*num);
int scale_size = int(1+pow(2.0,scale+1+super_res));
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);
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(angle,filtered_v,gray_image.size(),images[i+num]);
// rotate filtered images back
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]);
// calc feature map
calcFeatureMap(rotated_images[scale_id],feature_maps[scale_id]);
// filter feature map to improve impulse responses
if(parameters.filter)
{
cv::Mat high,low;
cv::blur(feature_maps[scale_id],low,cv::Size(scale_size,scale_size));
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;
}
}
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
if(parameters.filter)
{
cv::Mat high,low;
cv::blur(feature_maps[scale_id],low,cv::Size(scale_size,scale_size));
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
@ -1865,6 +1869,7 @@ cv::Point2f &Chessboard::Board::getCorner(int _row,int _col)
}while(_row);
}
CV_Error(Error::StsInternal,"cannot find corner");
// return *top_left->top_left; // never reached
}
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
#endif
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);
std::vector<KeyPoint> keypoints_seed;
std::vector<std::vector<float> > angles;
@ -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();
int count = 0;
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;
if(count >= inum)
break;
seed_iter = keypoints_seed.end();
return Chessboard::Board();
}
}
if(seed_iter == keypoints_seed.end())
return Chessboard::Board();
// just add dummy points or flann will fail during knnSearch
if(keypoints_seed.size() < 21)
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
std::vector<Board> boards;
generateBoards(flann_index, data,*points_iter,white_angle,black_angle,min_response,gray,boards);
std::vector<Chessboard::Board>::iterator iter_boards = boards.begin();
for(;iter_boards != boards.end();++iter_boards)
{
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)
parallel_for_(Range(0,(int)boards.size()),[&](const Range& range){
for(int i=range.start;i <range.end;++i)
{
if(!bounding_box.contains(*iter))
break;
}
if(iter != contour.end())
continue;
if(iter_boards->getSize() == parameters.chessboard_size ||
iter_boards->getSize() == chessboard_size2)
{
iter_boards->normalizeOrientation(false);
if(iter_boards->getSize() != parameters.chessboard_size)
auto iter_boards = boards.begin()+i;
cv::Mat h = iter_boards->estimateHomography();
int size = iter_boards->validateCorners(data,flann_index,h,min_response);
if(size != 9 || !iter_boards->validateContour())
{
if(iter_boards->isCellBlack(0,0) == iter_boards->isCellBlack(0,int(iter_boards->colCount())-1))
iter_boards->rotateLeft();
else
iter_boards->rotateRight();
iter_boards->clear();
continue;
}
//grow based on kd-tree
iter_boards->grow(data,flann_index);
if(!iter_boards->checkUnique())
{
iter_boards->clear();
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))
break;
}
if(iter != contour.end())
{
iter_boards->clear();
continue;
}
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
cv::Mat img;
iter_boards->draw(debug_image,img);
cv::imshow("chessboard",img);
cv::waitKey(-1);
cv::Mat img;
iter_boards->draw(debug_image,img);
cv::imshow("chessboard",img);
cv::waitKey(-1);
#endif
return *iter_boards;
}
else
{
if(iter_boards->getSize().width*iter_boards->getSize().height > chessboard_size2.width*chessboard_size2.height)
}
else
{
if(parameters.larger)
return *iter_boards;
else
return Chessboard::Board();
if(iter_boards->getSize().width*iter_boards->getSize().height < chessboard_size2.width*chessboard_size2.height)
iter_boards->clear();
else if(!parameters.larger)
iter_boards->clear();
}
}
});
// check if a good board was found
for(const auto &board : boards)
{
if(!board.isEmpty())
return board;
}
}
return Chessboard::Board();
@ -3175,24 +3184,32 @@ bool cv::findChessboardCornersSB(cv::InputArray image_, cv::Size pattern_size,
details::Chessboard::Parameters para;
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)
{
case 1: // high accuracy profile
para.min_scale = 2;
para.max_scale = 4;
para.max_tests = 100;
para.super_resolution = true;
para.max_points = std::max(500,pattern_size.width*pattern_size.height*2);
break;
default: // default profile
para.min_scale = 2;
para.max_scale = 3;
para.max_tests = 20;
para.max_points = pattern_size.width*pattern_size.height*2;
para.super_resolution = false;
break;
cv::equalizeHist(img,img);
flags ^= CALIB_CB_NORMALIZE_IMAGE;
}
if(flags & CALIB_CB_EXHAUSTIVE)
{
para.max_tests = 100;
para.max_points = std::max(500,pattern_size.width*pattern_size.height*2);
flags ^= CALIB_CB_EXHAUSTIVE;
}
if(flags & CALIB_CB_ACCURACY)
{
para.super_resolution = true;
flags ^= CALIB_CB_ACCURACY;
}
if(flags)
CV_Error(Error::StsOutOfRange, cv::format("Invalid remaing flags %d", (int)flags));
std::vector<cv::KeyPoint> corners;
details::Chessboard board(para);
board.detect(img,corners);