通过霍夫变换检测框
Detecting boxes via Hough Transform
我正在尝试使用 HoughTransform 检测框并提供不同的颜色。
到目前为止我的理解是一个方框是水平线和垂直线。
我的密码是
lines = cv2.HoughLines(edges,1,np.pi/180, 50)
# The below for loop runs till r and theta values
# are in the range of the 2d array
for r,theta in lines[0]:
# Stores the value of cos(theta) in a
a = np.cos(theta)
# Stores the value of sin(theta) in b
b = np.sin(theta)
# x0 stores the value rcos(theta)
x0 = a*r
# y0 stores the value rsin(theta)
y0 = b*r
# x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
x1 = int(x0 + 1000*(-b))
# y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
y1 = int(y0 + 1000*(a))
# x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
x2 = int(x0 - 1000*(-b))
# y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
y2 = int(y0 - 1000*(a))
# cv2.line draws a line in img from the point(x1,y1) to (x2,y2).
# (255,255,255) denotes the colour of the line to be.
cv2.line(img,(x1,y1), (x2,y2), (255,255,255),2) `
我该怎么做才能让盒子上色或识别?
垂直线和水平线的检测应该分开进行,这样才能更具体。
遍历所有线条并编制水平和垂直线条组合之间的交叉点列表
现在你有一个二维点列表,如果你绘制它们应该几乎在方框的角上。最后一步是将这些点收集到有意义的集合中。
为了得到这些集合,我会从最近的原点开始(只是为了从某个地方开始)。我会查看所有其他点,寻找最近的 other 点,该点具有更大的 x 但在起点的 +-5(或某个可配置范围)y 范围内。然后做同样的事情,但在 y 方向。你现在有了盒子的底角。您可以完成并开始您的 ocr,但为了更强大,还要找到最后一个角。
找到所有 4 个角后,从交集数组中删除所有这些点,然后添加要表示方框位置的新数组。冲洗并重复,因为现在不同的点将是最近的原点。在没有实际测试的情况下,我认为它会在 K 盒上阻塞(或需要对缺失的墙壁进行一些有条件的改进),但对于可变盒的形状和大小应该是非常通用的。
编辑 1:在测试中,我发现可能很难将两个相邻框的闭合角分开。我认为更通用和更强大的解决方案是在发生碰撞后,在大约 1/3 框最小边长处进行点聚类操作。这将平均角与最近的邻居一起。因此,这将稍微改变策略,因为您需要使用每个角两次(框向左和框向右),端点除外。
编写了一些测试代码并且可以正常运行,输出如下:
代码,对于 c++ 很抱歉,一点也没有优化,星期五快乐 :)
//CPP libaries
#include <stdio.h>
#include <mutex>
#include <thread>
//Included libraries
//Note: these headers have to be before any opencv due to a namespace collision (could probably be fixed)
#include <opencv2/opencv.hpp>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
// Finds the intersection of two lines, or returns false.
// The lines are defined by (o1, p1) and (o2, p2).
//
bool intersection(Point2f o1, Point2f p1, Point2f o2, Point2f p2,
Point2f &r)
{
Point2f x = o2 - o1;
Point2f d1 = p1 - o1;
Point2f d2 = p2 - o2;
float cross = d1.x*d2.y - d1.y*d2.x;
if (abs(cross) < /*EPS*/1e-8)
return false;
double t1 = (x.x * d2.y - x.y * d2.x) / cross;
r = o1 + d1 * t1;
return true;
}
std::vector<Point2f> clusterPts(std::vector<Point2f> inputPts, double clusterRadius_Squared)
{
std::vector<Point2f> outputPts = std::vector<Point2f>();
while(inputPts.size()>0)
{
Point2f clusterCenter = inputPts[0];
while (true)
{
Point2f newClustCenter = Point2f(0, 0);
int averagingCount = 0;
std::vector<int> clusterIndicies = std::vector<int>();
for (int i = 0; i < inputPts.size(); i++)
{
if (clusterRadius_Squared >= pow(inputPts[i].x - clusterCenter.x, 2) + pow(inputPts[i].y - clusterCenter.y, 2))
{
newClustCenter.x += inputPts[i].x;
newClustCenter.y += inputPts[i].y;
averagingCount += 1;
clusterIndicies.push_back(i);
}
}
newClustCenter = newClustCenter / (double)averagingCount;
if (newClustCenter == clusterCenter)
{
//remove all points inside cluster from inputPts, stash cluster center, and break inner while loop
std::vector<Point2f> remainingPts = std::vector<Point2f>();
for (int i = 0; i < inputPts.size(); i++)
{
if (std::find(clusterIndicies.begin(), clusterIndicies.end(), i) == clusterIndicies.end())
{
remainingPts.push_back(inputPts[i]);
}
}
inputPts = remainingPts;
outputPts.push_back(clusterCenter);
break;
}
else
{
clusterCenter = newClustCenter;
}
}
}
return outputPts;
}
std::vector<Rect> findBoxes(std::vector<Point2f> corners, bool shrinkBoxes = false, int boxSideLength_Guess = 50)
{
std::vector<Rect> outBoxes = std::vector<Rect>();
int approxBoxSize = 1000 * boxSideLength_Guess;
while (corners.size()>4)
{
//find point above or below (these points will be removed from array after used)
int secondPtIndex = -1;
for (int i = 1; i < corners.size(); i++)
{
if (abs(corners[i].x - corners[0].x) < boxSideLength_Guess / 2.0)
{
secondPtIndex = i;
break;
}
}
if (secondPtIndex == -1)
{
std::cout << "bad box point tossed" << std::endl;
corners.erase(corners.begin() + 0);
continue;
}
//now search for closest same level point on either side
int thirdIndexRight = -1;
int thirdIndexLeft = -1;
double minDistRight = approxBoxSize;
double minDistLeft = -approxBoxSize;
for (int i = 2; i < corners.size(); i++)
{
if (abs(corners[i].y - corners[secondPtIndex].y) < boxSideLength_Guess / 2.0)
{
double dist = corners[i].x - corners[secondPtIndex].x;
if (dist < 0 && dist > minDistLeft) //check left
{
minDistLeft = dist;
thirdIndexLeft = i;
}
else if(dist > 0 && dist < minDistRight) //check right
{
minDistRight = dist;
thirdIndexRight = i;
}
}
}
if (thirdIndexLeft != -1) { approxBoxSize = 1.5 * abs(minDistLeft); }
if (thirdIndexRight != -1) { approxBoxSize = 1.5 * minDistRight; }
int fourthIndexRight = -1;
int fourthIndexLeft = -1;
for (int i = 1; i < corners.size(); i++)
{
if (i == thirdIndexLeft || i == thirdIndexRight) { continue; }
if (thirdIndexLeft != -1 && abs(corners[i].x - corners[thirdIndexLeft].x) < boxSideLength_Guess / 2.0)
{ fourthIndexLeft = i; }
if (thirdIndexRight != -1 && abs(corners[i].x - corners[thirdIndexRight].x) < boxSideLength_Guess / 2.0)
{ fourthIndexRight = i; }
}
if (!shrinkBoxes)
{
if (fourthIndexRight != -1)
{
outBoxes.push_back(Rect(corners[0], corners[thirdIndexRight]));
}
if (fourthIndexLeft != -1)
{
outBoxes.push_back(Rect(corners[0], corners[thirdIndexLeft]));
}
}
else
{
if (fourthIndexRight != -1)
{
outBoxes.push_back(Rect(corners[0] * 0.90 + corners[thirdIndexRight] *0.10, corners[0] * 0.10 + corners[thirdIndexRight] * 0.90));
}
if (fourthIndexLeft != -1)
{
outBoxes.push_back(Rect(corners[0] * 0.90 + corners[thirdIndexLeft] * 0.10, corners[0] * 0.10 + corners[thirdIndexLeft] * 0.90));
}
}
corners.erase(corners.begin() + secondPtIndex);
corners.erase(corners.begin() + 0);
}
std::cout << approxBoxSize << std::endl;
return outBoxes;
}
int main(int argc, char** argv)
{
Mat image = imread("../../resources/images/boxPic.png", CV_LOAD_IMAGE_GRAYSCALE);
imshow("source", image);
//namedWindow("Display window", WINDOW_AUTOSIZE);// Create a window for display.
//imshow("Display window", image); // Show our image inside it.
Mat edges, lineOverlay, cornerOverlay, finalBoxes;
Canny(image, edges, 50, 200, 3);
//edges = image;
//cvtColor(image, edges, COLOR_GRAY2BGR);
cvtColor(image, lineOverlay, COLOR_GRAY2BGR);
cvtColor(image, cornerOverlay, COLOR_GRAY2BGR);
cvtColor(image, finalBoxes, COLOR_GRAY2BGR);
std::cout << image.cols << " , "<<image.rows << std::endl;
std::vector<Vec2f> linesHorizontal;
std::vector<Point> ptsLH;
HoughLines(edges, linesHorizontal, 5, CV_PI / 180, 2 * edges.cols * 0.6, 0.0,0.0, CV_PI / 4, 3 * CV_PI / 4);
std::vector<Vec2f> linesVertical;
std::vector<Point> ptsLV;
HoughLines(edges, linesVertical, 5, CV_PI / 180, 2 * edges.rows * 0.6,0,0,-CV_PI/32,CV_PI/32);
for (size_t i = 0; i < linesHorizontal.size(); i++)
{
float rho = linesHorizontal[i][0], theta = linesHorizontal[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
ptsLH.push_back(pt1);
ptsLH.push_back(pt2);
line(lineOverlay, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
}
for (size_t i = 0; i < linesVertical.size(); i++)
{
float rho = linesVertical[i][0], theta = linesVertical[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
ptsLV.push_back(pt1);
ptsLV.push_back(pt2);
line(lineOverlay, pt1, pt2, Scalar(0, 255, 0), 1, LINE_AA);
}
imshow("edged", edges);
imshow("detected lines", lineOverlay);
//look for collisions
std::vector<Point2f> xPts;
for (size_t i = 0; i < linesHorizontal.size(); i++)
{
for (size_t ii = 0; ii < linesVertical.size(); ii++)
{
Point2f xPt;
bool intersectionExists = intersection(ptsLH[2 * i], ptsLH[2 * i + 1], ptsLV[2 * ii], ptsLV[2 * ii + 1], xPt);
if (intersectionExists)
{
xPts.push_back(xPt);
}
}
}
waitKey(1000);
std::vector<Point2f> boxCorners = clusterPts(xPts, 25*25);
for (int i = 0; i < boxCorners.size(); i++)
{
circle(cornerOverlay, boxCorners[i], 5, Scalar(0, 255, 0), 2);
}
imshow("detected corners", cornerOverlay);
//group make boxes for groups of points
std::vector<Rect> ocrBoxes = findBoxes(boxCorners,true);
for (int i = 0; i < ocrBoxes.size(); i++)
{
if (i % 3 == 0) { rectangle(finalBoxes, ocrBoxes[i], Scalar(255, 0, 0), 2); }
else if(i % 3 == 1) { rectangle(finalBoxes, ocrBoxes[i], Scalar(0, 255, 0), 2); }
else if (i % 3 == 2) { rectangle(finalBoxes, ocrBoxes[i], Scalar(0, 0, 255), 2); }
}
imshow("detected boxes", finalBoxes);
waitKey(0); // Wait for a keystroke in the window
return 0;
}
我正在尝试使用 HoughTransform 检测框并提供不同的颜色。
到目前为止我的理解是一个方框是水平线和垂直线。
我的密码是
lines = cv2.HoughLines(edges,1,np.pi/180, 50)
# The below for loop runs till r and theta values
# are in the range of the 2d array
for r,theta in lines[0]:
# Stores the value of cos(theta) in a
a = np.cos(theta)
# Stores the value of sin(theta) in b
b = np.sin(theta)
# x0 stores the value rcos(theta)
x0 = a*r
# y0 stores the value rsin(theta)
y0 = b*r
# x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
x1 = int(x0 + 1000*(-b))
# y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
y1 = int(y0 + 1000*(a))
# x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
x2 = int(x0 - 1000*(-b))
# y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
y2 = int(y0 - 1000*(a))
# cv2.line draws a line in img from the point(x1,y1) to (x2,y2).
# (255,255,255) denotes the colour of the line to be.
cv2.line(img,(x1,y1), (x2,y2), (255,255,255),2) `
我该怎么做才能让盒子上色或识别?
垂直线和水平线的检测应该分开进行,这样才能更具体。
遍历所有线条并编制水平和垂直线条组合之间的交叉点列表
现在你有一个二维点列表,如果你绘制它们应该几乎在方框的角上。最后一步是将这些点收集到有意义的集合中。
为了得到这些集合,我会从最近的原点开始(只是为了从某个地方开始)。我会查看所有其他点,寻找最近的 other 点,该点具有更大的 x 但在起点的 +-5(或某个可配置范围)y 范围内。然后做同样的事情,但在 y 方向。你现在有了盒子的底角。您可以完成并开始您的 ocr,但为了更强大,还要找到最后一个角。
找到所有 4 个角后,从交集数组中删除所有这些点,然后添加要表示方框位置的新数组。冲洗并重复,因为现在不同的点将是最近的原点。在没有实际测试的情况下,我认为它会在 K 盒上阻塞(或需要对缺失的墙壁进行一些有条件的改进),但对于可变盒的形状和大小应该是非常通用的。
编辑 1:在测试中,我发现可能很难将两个相邻框的闭合角分开。我认为更通用和更强大的解决方案是在发生碰撞后,在大约 1/3 框最小边长处进行点聚类操作。这将平均角与最近的邻居一起。因此,这将稍微改变策略,因为您需要使用每个角两次(框向左和框向右),端点除外。
编写了一些测试代码并且可以正常运行,输出如下:
代码,对于 c++ 很抱歉,一点也没有优化,星期五快乐 :)
//CPP libaries
#include <stdio.h>
#include <mutex>
#include <thread>
//Included libraries
//Note: these headers have to be before any opencv due to a namespace collision (could probably be fixed)
#include <opencv2/opencv.hpp>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
// Finds the intersection of two lines, or returns false.
// The lines are defined by (o1, p1) and (o2, p2).
//
bool intersection(Point2f o1, Point2f p1, Point2f o2, Point2f p2,
Point2f &r)
{
Point2f x = o2 - o1;
Point2f d1 = p1 - o1;
Point2f d2 = p2 - o2;
float cross = d1.x*d2.y - d1.y*d2.x;
if (abs(cross) < /*EPS*/1e-8)
return false;
double t1 = (x.x * d2.y - x.y * d2.x) / cross;
r = o1 + d1 * t1;
return true;
}
std::vector<Point2f> clusterPts(std::vector<Point2f> inputPts, double clusterRadius_Squared)
{
std::vector<Point2f> outputPts = std::vector<Point2f>();
while(inputPts.size()>0)
{
Point2f clusterCenter = inputPts[0];
while (true)
{
Point2f newClustCenter = Point2f(0, 0);
int averagingCount = 0;
std::vector<int> clusterIndicies = std::vector<int>();
for (int i = 0; i < inputPts.size(); i++)
{
if (clusterRadius_Squared >= pow(inputPts[i].x - clusterCenter.x, 2) + pow(inputPts[i].y - clusterCenter.y, 2))
{
newClustCenter.x += inputPts[i].x;
newClustCenter.y += inputPts[i].y;
averagingCount += 1;
clusterIndicies.push_back(i);
}
}
newClustCenter = newClustCenter / (double)averagingCount;
if (newClustCenter == clusterCenter)
{
//remove all points inside cluster from inputPts, stash cluster center, and break inner while loop
std::vector<Point2f> remainingPts = std::vector<Point2f>();
for (int i = 0; i < inputPts.size(); i++)
{
if (std::find(clusterIndicies.begin(), clusterIndicies.end(), i) == clusterIndicies.end())
{
remainingPts.push_back(inputPts[i]);
}
}
inputPts = remainingPts;
outputPts.push_back(clusterCenter);
break;
}
else
{
clusterCenter = newClustCenter;
}
}
}
return outputPts;
}
std::vector<Rect> findBoxes(std::vector<Point2f> corners, bool shrinkBoxes = false, int boxSideLength_Guess = 50)
{
std::vector<Rect> outBoxes = std::vector<Rect>();
int approxBoxSize = 1000 * boxSideLength_Guess;
while (corners.size()>4)
{
//find point above or below (these points will be removed from array after used)
int secondPtIndex = -1;
for (int i = 1; i < corners.size(); i++)
{
if (abs(corners[i].x - corners[0].x) < boxSideLength_Guess / 2.0)
{
secondPtIndex = i;
break;
}
}
if (secondPtIndex == -1)
{
std::cout << "bad box point tossed" << std::endl;
corners.erase(corners.begin() + 0);
continue;
}
//now search for closest same level point on either side
int thirdIndexRight = -1;
int thirdIndexLeft = -1;
double minDistRight = approxBoxSize;
double minDistLeft = -approxBoxSize;
for (int i = 2; i < corners.size(); i++)
{
if (abs(corners[i].y - corners[secondPtIndex].y) < boxSideLength_Guess / 2.0)
{
double dist = corners[i].x - corners[secondPtIndex].x;
if (dist < 0 && dist > minDistLeft) //check left
{
minDistLeft = dist;
thirdIndexLeft = i;
}
else if(dist > 0 && dist < minDistRight) //check right
{
minDistRight = dist;
thirdIndexRight = i;
}
}
}
if (thirdIndexLeft != -1) { approxBoxSize = 1.5 * abs(minDistLeft); }
if (thirdIndexRight != -1) { approxBoxSize = 1.5 * minDistRight; }
int fourthIndexRight = -1;
int fourthIndexLeft = -1;
for (int i = 1; i < corners.size(); i++)
{
if (i == thirdIndexLeft || i == thirdIndexRight) { continue; }
if (thirdIndexLeft != -1 && abs(corners[i].x - corners[thirdIndexLeft].x) < boxSideLength_Guess / 2.0)
{ fourthIndexLeft = i; }
if (thirdIndexRight != -1 && abs(corners[i].x - corners[thirdIndexRight].x) < boxSideLength_Guess / 2.0)
{ fourthIndexRight = i; }
}
if (!shrinkBoxes)
{
if (fourthIndexRight != -1)
{
outBoxes.push_back(Rect(corners[0], corners[thirdIndexRight]));
}
if (fourthIndexLeft != -1)
{
outBoxes.push_back(Rect(corners[0], corners[thirdIndexLeft]));
}
}
else
{
if (fourthIndexRight != -1)
{
outBoxes.push_back(Rect(corners[0] * 0.90 + corners[thirdIndexRight] *0.10, corners[0] * 0.10 + corners[thirdIndexRight] * 0.90));
}
if (fourthIndexLeft != -1)
{
outBoxes.push_back(Rect(corners[0] * 0.90 + corners[thirdIndexLeft] * 0.10, corners[0] * 0.10 + corners[thirdIndexLeft] * 0.90));
}
}
corners.erase(corners.begin() + secondPtIndex);
corners.erase(corners.begin() + 0);
}
std::cout << approxBoxSize << std::endl;
return outBoxes;
}
int main(int argc, char** argv)
{
Mat image = imread("../../resources/images/boxPic.png", CV_LOAD_IMAGE_GRAYSCALE);
imshow("source", image);
//namedWindow("Display window", WINDOW_AUTOSIZE);// Create a window for display.
//imshow("Display window", image); // Show our image inside it.
Mat edges, lineOverlay, cornerOverlay, finalBoxes;
Canny(image, edges, 50, 200, 3);
//edges = image;
//cvtColor(image, edges, COLOR_GRAY2BGR);
cvtColor(image, lineOverlay, COLOR_GRAY2BGR);
cvtColor(image, cornerOverlay, COLOR_GRAY2BGR);
cvtColor(image, finalBoxes, COLOR_GRAY2BGR);
std::cout << image.cols << " , "<<image.rows << std::endl;
std::vector<Vec2f> linesHorizontal;
std::vector<Point> ptsLH;
HoughLines(edges, linesHorizontal, 5, CV_PI / 180, 2 * edges.cols * 0.6, 0.0,0.0, CV_PI / 4, 3 * CV_PI / 4);
std::vector<Vec2f> linesVertical;
std::vector<Point> ptsLV;
HoughLines(edges, linesVertical, 5, CV_PI / 180, 2 * edges.rows * 0.6,0,0,-CV_PI/32,CV_PI/32);
for (size_t i = 0; i < linesHorizontal.size(); i++)
{
float rho = linesHorizontal[i][0], theta = linesHorizontal[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
ptsLH.push_back(pt1);
ptsLH.push_back(pt2);
line(lineOverlay, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
}
for (size_t i = 0; i < linesVertical.size(); i++)
{
float rho = linesVertical[i][0], theta = linesVertical[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
ptsLV.push_back(pt1);
ptsLV.push_back(pt2);
line(lineOverlay, pt1, pt2, Scalar(0, 255, 0), 1, LINE_AA);
}
imshow("edged", edges);
imshow("detected lines", lineOverlay);
//look for collisions
std::vector<Point2f> xPts;
for (size_t i = 0; i < linesHorizontal.size(); i++)
{
for (size_t ii = 0; ii < linesVertical.size(); ii++)
{
Point2f xPt;
bool intersectionExists = intersection(ptsLH[2 * i], ptsLH[2 * i + 1], ptsLV[2 * ii], ptsLV[2 * ii + 1], xPt);
if (intersectionExists)
{
xPts.push_back(xPt);
}
}
}
waitKey(1000);
std::vector<Point2f> boxCorners = clusterPts(xPts, 25*25);
for (int i = 0; i < boxCorners.size(); i++)
{
circle(cornerOverlay, boxCorners[i], 5, Scalar(0, 255, 0), 2);
}
imshow("detected corners", cornerOverlay);
//group make boxes for groups of points
std::vector<Rect> ocrBoxes = findBoxes(boxCorners,true);
for (int i = 0; i < ocrBoxes.size(); i++)
{
if (i % 3 == 0) { rectangle(finalBoxes, ocrBoxes[i], Scalar(255, 0, 0), 2); }
else if(i % 3 == 1) { rectangle(finalBoxes, ocrBoxes[i], Scalar(0, 255, 0), 2); }
else if (i % 3 == 2) { rectangle(finalBoxes, ocrBoxes[i], Scalar(0, 0, 255), 2); }
}
imshow("detected boxes", finalBoxes);
waitKey(0); // Wait for a keystroke in the window
return 0;
}