有没有办法保证使用 cv2.HoughLines() 检测到一定数量的行?
Is there a way to guarantee a certain number of lines detected with cv2.HoughLines()?
这道题是我之前question asking about how to detect a pool table's corners. I have found the outline的一道题table的延伸题,我已经设法在轮廓上应用了霍夫变换。这个霍夫变换的结果如下:
不幸的是,Hough 变换 return 一条 table 边的多条线。我希望 Hough 变换为 return 四行,每行对应于给定池 table 的任何图像的 table 的边缘。我不想手动调整 Hough 变换方法的参数(因为池 table 的每个图像的池 table 的轮廓可能不同)。有什么方法可以保证 cv2.HoughLines()?
生成四行
提前致谢。
编辑
根据@fana 的评论,我使用以下代码创建了梯度方向直方图。我仍然不完全确定如何从此直方图中获得四行。
img = cv2.imread("Assets/Setup.jpg")
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
masked_img = cv2.inRange(hsv_img, (50, 40, 40), (70, 255, 255))
gaussian_blur_img = cv2.GaussianBlur(masked_img, (5, 5), 0)
sobel_x = np.asarray([[1, 0, -1], [2, 0, -2], [1, 0, -1]], dtype=np.int8)
sobel_y = np.asarray([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], dtype=np.int8)
gradient_x = cv2.filter2D(gaussian_blur_img, cv2.CV_16S, cv2.flip(sobel_x, -1), borderType=cv2.BORDER_CONSTANT)
gradient_y = cv2.filter2D(gaussian_blur_img, cv2.CV_16S, cv2.flip(sobel_y, -1), borderType=cv2.BORDER_CONSTANT)
edges = cv2.normalize(np.hypot(gradient_x, gradient_y), None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
edge_direction = np.arctan2(gradient_y, gradient_x) * (180 / np.pi)
edge_direction[edge_direction < 0] += 360
np.around(edge_direction, 0, edge_direction)
edge_direction[edge_direction == 360] = 0
edge_direction = edge_direction.astype("uint16")
histogram, bins = np.histogram(edge_direction, 359)
Using @fana's comments, I have created a histogram of gradient directions with the code below. I'm still not entirely sure how to obtain four lines from this histogram.
我试了一下
因为我不懂python,下面的示例代码是C++的。不过做了什么都写成注释了,看来你也能看懂了。
本样本包含以下内容:
- 提取水池的轮廓table。
- 创建Gradient-Direction 直方图(使用 Sobel 滤波器估计梯度)。
- 根据直方图峰值查找像素组。
此示例不包括 line-fitting 过程。
看分组结果,似乎有些像素会因为线拟合而变得离群。因此,我认为最好使用一些稳健的拟合方法(例如M-estimator,RANSAC)。
int main()
{
//I obtained this image from your previous question.
//However, I do not used as it is.
//This image "PoolTable.png" is 25% scale version.
//(Because your original image was too large for my monitor!)
cv::Mat SrcImg = cv::imread( "PoolTable.png" ); //Size is 393x524[pixel]
if( SrcImg.empty() )return 0;
//Extract Outline Pixels
std::vector< cv::Point > OutlinePixels;
{
//Here, I adjusted a little.
// - Change argument value for inRange
// - Emplying morphologyEx() additionally.
cv::Mat HSVImg;
cv::cvtColor( SrcImg, HSVImg, cv::COLOR_BGR2HSV );
cv::Mat Mask;
cv::inRange( HSVImg, cv::Scalar(40,40,40), cv::Scalar(80,255,255), Mask );
cv::morphologyEx( Mask, Mask, cv::MORPH_OPEN, cv::Mat() );
//Here, outline is found as the contour which has max area.
std::vector< std::vector<cv::Point> > contours;
cv::findContours( Mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE );
if( contours.empty() )return 0;
int MaxAreaIndex = 0;
double MaxArea=0;
for( int iContour=0; iContour<contours.size(); ++iContour )
{
double Area = cv::contourArea( contours[iContour] );
if( MaxArea < Area ){ MaxArea = Area; MaxAreaIndex = iContour; }
}
OutlinePixels = contours[MaxAreaIndex];
}
//Sobel
cv::Mat Gx,Gy;
{
const int KernelSize = 5;
cv::Mat GraySrc;
cv::cvtColor( SrcImg, GraySrc, cv::COLOR_BGR2GRAY );
cv::Sobel( GraySrc, Gx, CV_32F, 1,0, KernelSize );
cv::Sobel( GraySrc, Gy, CV_32F, 0,1, KernelSize );
}
//Voting
// Here, each element is the vector of index of point.
// (Make it possible to know which pixel voted where.)
std::vector<int> VotingSpace[360]; //360 Bins
for( int iPoint=0; iPoint<OutlinePixels.size(); ++iPoint ) //for all outline pixels
{
const cv::Point &P = OutlinePixels[iPoint];
float gx = Gx.at<float>(P);
float gy = Gy.at<float>(P);
//(Ignore this pixel if magnitude of gradient is weak.)
if( gx*gx + gy*gy < 100*100 )continue;
//Determine the bin to vote based on the angle
double angle_rad = atan2( gy,gx );
double angle_deg = angle_rad * 180.0 / CV_PI;
int BinIndex = cvRound(angle_deg);
if( BinIndex<0 )BinIndex += 360;
if( BinIndex>=360 )BinIndex -= 360;
//Vote
VotingSpace[ BinIndex ].push_back( iPoint );
}
//Find Pixel-Groups Based on Voting Result.
std::vector< std::vector<cv::Point> > PixelGroups;
{
//- Create Blurred Vote count (used for threshold at next process)
//- Find the bin with the fewest votes (used for start bin of serching at next process)
unsigned int BlurredVotes[360];
int MinIndex = 0;
{
const int r = 10; //(blur-kernel-radius)
unsigned int MinVoteVal = VotingSpace[MinIndex].size();
for( int i=0; i<360; ++i )
{
//blur
unsigned int Sum = 0;
for( int k=i-r; k<=i+r; ++k ){ Sum += VotingSpace[ (k<0 ? k+360 : (k>=360 ? k-360 : k)) ].size(); }
BlurredVotes[i] = (int)( 0.5 + (double)Sum / (2*r+1) );
//find min
if( MinVoteVal > VotingSpace[i].size() ){ MinVoteVal = VotingSpace[i].size(); MinIndex = i; }
}
}
//Find Pixel-Groups
// Search is started from the bin with the fewest votes.
// (Expect the starting bin to not belong to any group.)
std::vector<cv::Point> Pixels_Voted_to_SameLine;
const int ThreshOffset = 5;
for( int i=0; i<360; ++i )
{
int k = (MinIndex + i)%360;
if( VotingSpace[k].size() <= BlurredVotes[k]+ThreshOffset )
{
if( !Pixels_Voted_to_SameLine.empty() )
{//The end of the group was found
PixelGroups.push_back( Pixels_Voted_to_SameLine );
Pixels_Voted_to_SameLine.clear();
}
}
else
{//Add pixels which voted to Bin[k] to current group
for( int iPixel : VotingSpace[k] )
{ Pixels_Voted_to_SameLine.push_back( OutlinePixels[iPixel] ); }
}
}
if( !Pixels_Voted_to_SameLine.empty() )
{ PixelGroups.push_back( Pixels_Voted_to_SameLine ); }
//This line is just show the number of groups.
//(When I execute this code, 4 groups found.)
std::cout << PixelGroups.size() << " groups found." << std::endl;
}
{//Draw Pixel Groups to check result
cv::Mat ShowImg = SrcImg * 0.2;
for( int iGroup=0; iGroup<PixelGroups.size(); ++iGroup )
{
const cv::Vec3b DrawColor{
unsigned char( ( (iGroup+1) & 0x4) ? 255 : 80 ),
unsigned char( ( (iGroup+1) & 0x2) ? 255 : 80 ),
unsigned char( ( (iGroup+1) & 0x1) ? 255 : 80 )
};
for( const auto &P : PixelGroups[iGroup] ){ ShowImg.at<cv::Vec3b>(P) = DrawColor; }
}
cv::imshow( "GroupResult", ShowImg );
if( cv::waitKey() == 's' ){ cv::imwrite( "GroupResult.png", ShowImg ); }
}
return 0;
}
结果图片:
找到 4 组,并且属于同一组的像素以相同的颜色绘制。 (红、绿、蓝和黄色)
这道题是我之前question asking about how to detect a pool table's corners. I have found the outline的一道题table的延伸题,我已经设法在轮廓上应用了霍夫变换。这个霍夫变换的结果如下:
不幸的是,Hough 变换 return 一条 table 边的多条线。我希望 Hough 变换为 return 四行,每行对应于给定池 table 的任何图像的 table 的边缘。我不想手动调整 Hough 变换方法的参数(因为池 table 的每个图像的池 table 的轮廓可能不同)。有什么方法可以保证 cv2.HoughLines()?
提前致谢。
编辑
根据@fana 的评论,我使用以下代码创建了梯度方向直方图。我仍然不完全确定如何从此直方图中获得四行。
img = cv2.imread("Assets/Setup.jpg")
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
masked_img = cv2.inRange(hsv_img, (50, 40, 40), (70, 255, 255))
gaussian_blur_img = cv2.GaussianBlur(masked_img, (5, 5), 0)
sobel_x = np.asarray([[1, 0, -1], [2, 0, -2], [1, 0, -1]], dtype=np.int8)
sobel_y = np.asarray([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], dtype=np.int8)
gradient_x = cv2.filter2D(gaussian_blur_img, cv2.CV_16S, cv2.flip(sobel_x, -1), borderType=cv2.BORDER_CONSTANT)
gradient_y = cv2.filter2D(gaussian_blur_img, cv2.CV_16S, cv2.flip(sobel_y, -1), borderType=cv2.BORDER_CONSTANT)
edges = cv2.normalize(np.hypot(gradient_x, gradient_y), None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
edge_direction = np.arctan2(gradient_y, gradient_x) * (180 / np.pi)
edge_direction[edge_direction < 0] += 360
np.around(edge_direction, 0, edge_direction)
edge_direction[edge_direction == 360] = 0
edge_direction = edge_direction.astype("uint16")
histogram, bins = np.histogram(edge_direction, 359)
Using @fana's comments, I have created a histogram of gradient directions with the code below. I'm still not entirely sure how to obtain four lines from this histogram.
我试了一下
因为我不懂python,下面的示例代码是C++的。不过做了什么都写成注释了,看来你也能看懂了。
本样本包含以下内容:
- 提取水池的轮廓table。
- 创建Gradient-Direction 直方图(使用 Sobel 滤波器估计梯度)。
- 根据直方图峰值查找像素组。
此示例不包括 line-fitting 过程。
看分组结果,似乎有些像素会因为线拟合而变得离群。因此,我认为最好使用一些稳健的拟合方法(例如M-estimator,RANSAC)。
int main()
{
//I obtained this image from your previous question.
//However, I do not used as it is.
//This image "PoolTable.png" is 25% scale version.
//(Because your original image was too large for my monitor!)
cv::Mat SrcImg = cv::imread( "PoolTable.png" ); //Size is 393x524[pixel]
if( SrcImg.empty() )return 0;
//Extract Outline Pixels
std::vector< cv::Point > OutlinePixels;
{
//Here, I adjusted a little.
// - Change argument value for inRange
// - Emplying morphologyEx() additionally.
cv::Mat HSVImg;
cv::cvtColor( SrcImg, HSVImg, cv::COLOR_BGR2HSV );
cv::Mat Mask;
cv::inRange( HSVImg, cv::Scalar(40,40,40), cv::Scalar(80,255,255), Mask );
cv::morphologyEx( Mask, Mask, cv::MORPH_OPEN, cv::Mat() );
//Here, outline is found as the contour which has max area.
std::vector< std::vector<cv::Point> > contours;
cv::findContours( Mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE );
if( contours.empty() )return 0;
int MaxAreaIndex = 0;
double MaxArea=0;
for( int iContour=0; iContour<contours.size(); ++iContour )
{
double Area = cv::contourArea( contours[iContour] );
if( MaxArea < Area ){ MaxArea = Area; MaxAreaIndex = iContour; }
}
OutlinePixels = contours[MaxAreaIndex];
}
//Sobel
cv::Mat Gx,Gy;
{
const int KernelSize = 5;
cv::Mat GraySrc;
cv::cvtColor( SrcImg, GraySrc, cv::COLOR_BGR2GRAY );
cv::Sobel( GraySrc, Gx, CV_32F, 1,0, KernelSize );
cv::Sobel( GraySrc, Gy, CV_32F, 0,1, KernelSize );
}
//Voting
// Here, each element is the vector of index of point.
// (Make it possible to know which pixel voted where.)
std::vector<int> VotingSpace[360]; //360 Bins
for( int iPoint=0; iPoint<OutlinePixels.size(); ++iPoint ) //for all outline pixels
{
const cv::Point &P = OutlinePixels[iPoint];
float gx = Gx.at<float>(P);
float gy = Gy.at<float>(P);
//(Ignore this pixel if magnitude of gradient is weak.)
if( gx*gx + gy*gy < 100*100 )continue;
//Determine the bin to vote based on the angle
double angle_rad = atan2( gy,gx );
double angle_deg = angle_rad * 180.0 / CV_PI;
int BinIndex = cvRound(angle_deg);
if( BinIndex<0 )BinIndex += 360;
if( BinIndex>=360 )BinIndex -= 360;
//Vote
VotingSpace[ BinIndex ].push_back( iPoint );
}
//Find Pixel-Groups Based on Voting Result.
std::vector< std::vector<cv::Point> > PixelGroups;
{
//- Create Blurred Vote count (used for threshold at next process)
//- Find the bin with the fewest votes (used for start bin of serching at next process)
unsigned int BlurredVotes[360];
int MinIndex = 0;
{
const int r = 10; //(blur-kernel-radius)
unsigned int MinVoteVal = VotingSpace[MinIndex].size();
for( int i=0; i<360; ++i )
{
//blur
unsigned int Sum = 0;
for( int k=i-r; k<=i+r; ++k ){ Sum += VotingSpace[ (k<0 ? k+360 : (k>=360 ? k-360 : k)) ].size(); }
BlurredVotes[i] = (int)( 0.5 + (double)Sum / (2*r+1) );
//find min
if( MinVoteVal > VotingSpace[i].size() ){ MinVoteVal = VotingSpace[i].size(); MinIndex = i; }
}
}
//Find Pixel-Groups
// Search is started from the bin with the fewest votes.
// (Expect the starting bin to not belong to any group.)
std::vector<cv::Point> Pixels_Voted_to_SameLine;
const int ThreshOffset = 5;
for( int i=0; i<360; ++i )
{
int k = (MinIndex + i)%360;
if( VotingSpace[k].size() <= BlurredVotes[k]+ThreshOffset )
{
if( !Pixels_Voted_to_SameLine.empty() )
{//The end of the group was found
PixelGroups.push_back( Pixels_Voted_to_SameLine );
Pixels_Voted_to_SameLine.clear();
}
}
else
{//Add pixels which voted to Bin[k] to current group
for( int iPixel : VotingSpace[k] )
{ Pixels_Voted_to_SameLine.push_back( OutlinePixels[iPixel] ); }
}
}
if( !Pixels_Voted_to_SameLine.empty() )
{ PixelGroups.push_back( Pixels_Voted_to_SameLine ); }
//This line is just show the number of groups.
//(When I execute this code, 4 groups found.)
std::cout << PixelGroups.size() << " groups found." << std::endl;
}
{//Draw Pixel Groups to check result
cv::Mat ShowImg = SrcImg * 0.2;
for( int iGroup=0; iGroup<PixelGroups.size(); ++iGroup )
{
const cv::Vec3b DrawColor{
unsigned char( ( (iGroup+1) & 0x4) ? 255 : 80 ),
unsigned char( ( (iGroup+1) & 0x2) ? 255 : 80 ),
unsigned char( ( (iGroup+1) & 0x1) ? 255 : 80 )
};
for( const auto &P : PixelGroups[iGroup] ){ ShowImg.at<cv::Vec3b>(P) = DrawColor; }
}
cv::imshow( "GroupResult", ShowImg );
if( cv::waitKey() == 's' ){ cv::imwrite( "GroupResult.png", ShowImg ); }
}
return 0;
}
结果图片: 找到 4 组,并且属于同一组的像素以相同的颜色绘制。 (红、绿、蓝和黄色)