这样两张图片,第二张的识别就很简单了,图像像素少。本篇写第一张图片的识别,基本思路是 识别轮廓->轮廓直线拟合->计算交点坐标->计算中心坐标。有人跟我说用骨架细化的算法比较简单,下次我在写骨架细化,先做了第一种比较笨的方法实现的,可能有点麻烦,但这是我拿到图像的第一思路。最后用QT做了一个窗口来输出。
完整版代码在最后。
1.轮廓识别
拿到图像发现周围有一圈黑色的,这样是无法识别的,先转灰度图,然后转二值图,设置一下阈值就行了。然后用canny函数识别轮廓。
cvtColor( src, image_gray, CV_BGR2GRAY );//灰度化
threshold(image_gray, src, 110, 255, THRESH_TOZERO);//二值化
这是二值化后的图像,关于opencv中阈值的设置,以后在记录,也是看了一些博客才学会用法,255是白色,0是黑色,我这里设置的阈值是110,THRESH_TOZERO将像素值低于110变成0,其余的保留。为什么设置成110,其实是试了很久,有好几组测试用例,测试了半天,阈值的大小会影响最后我识别的轮廓精度...所以我这里设置成110最合适,换一组图片不阈值都需要改的。
GaussianBlur( src, src, Size(3,3), 0.1, 0, BORDER_DEFAULT );
blur( src, src, Size(3,3) ); //滤波
Canny( src, src, thresh, thresh*3, 3 );
canny识别轮廓,滤波去噪。thresh我设置的值是45,写在头文件里了。大概识别出来图像就是这样的,很乱。。。主要是图片本身就这样的,我也很无奈啊,原图真的挺大的,是bmp格式,我这里都放的缩略图,图像低一点像素都不会这样。
2.轮廓直线拟合
Mat contours1;
Canny(src,contours1,125,350);
std::vector lines;//存放绘制线条,lines[0]代表了极坐标中r ,lines[1]代表了极坐标中角度theta
//调用hough函数
HoughLines(contours1,lines,1,PI/180,305);
threshold(image_gray, image_gray, 255, 255, THRESH_BINARY_INV);
imwrite("E:\\white3.jpg",image_gray);//将lines放到空白图像中,图像继承原图像的长宽。
Mat hough_output = imread("E:\\white3.jpg",CV_LOAD_IMAGE_COLOR);
std::vector::const_iterator it = lines.begin();
while(it != lines.end())
{
float rho = (*it)[0];
float theta=(*it)[1];
if(theta < PI/4. || theta > 3. *PI / 4.)
{
//接近于垂直线条
Point pt1(rho/cos(theta),0);
Point pt2((rho-hough_output.rows*sin(theta))/cos(theta),hough_output.rows);
line(hough_output,pt1,pt2,Scalar(255),1);
}
else
{
//接近于水平线 r=xcos(theta)+ysin(theta)
Point pt1(0,rho/sin(theta));
Point pt2(hough_output.cols,(rho-hough_output.cols*cos(theta))/sin(theta));
line(hough_output,pt1,pt2,Scalar(255),1);
}
++it;
}
将轮廓的线拟合成一条,霍夫变换调用这里设置的阈值是305,反正就是越大识别越精确。这样是不是看着舒服多了~但是这里有一个问题,因为huogh调用完后是在原图上输出,还会有弯弯曲曲的边,我就设置了一张空白的图像,图像大小是原来的图像大小,用二值化生成的,将所有像素都变成255,挺笨的。。但是我不知道用什么方法可以去除源图像中的canny。关于函数具体的计算,可以查看别的大佬的博客,我也是看了很多博客找到的用法,大概就是识别一个像素点的周围8个像素中的点,连线在走下一个是否在一个直线上,一个一个点去判断,阈值设置最短直线长度,其他的短线都去掉了。直线方程是用极坐标的,在下面的while中绘制线条到图像时,起点分为x轴和y轴两个,用极坐标方程求出与x轴y轴交点,再判断下一个点。
HoughLines(contours1,lines,1,PI/180,305);//阈值305
将305阈值换成100的结果如下图。
3.计算交点坐标
首先先处理之前做好的一个轮廓图像,这里用到了霍夫的第二个函数,HoughLinesp:统计概率霍夫线变换
简单来说就是HoughLines是用极坐标识别了整条线上的点到原点的r和theta,但是HoughLinesP是识别了这条线上的两个端点。(HoughLinesP是在HoughLines的基础上加了一个概率P,表明可以采用累积概率霍夫变换来找出二值图中的直线。HoughLinesP的第二个参数可以得到直线的两个端点坐标,这点是优势。累积概率霍夫变换相比于标准和多尺度计算量小,执行效率高)
这里计算交点因此需要算出直线斜率然后找点。(我知道效率很低,虽然可以直接用HoughLines来求交点,目前第一版我是这样做的,后期再修改吧);这里有一个sidewide的计算,是需求里要求计算十字架边宽度的。
Mat houghGray;
Mat houghthre;
cvtColor(hough_output, houghGray, CV_BGR2GRAY);//转灰度
threshold(houghGray, houghthre, 100, 255, THRESH_BINARY);//二值化
Canny( houghthre, houghthre, thresh, thresh*3, 3 );//识别轮廓
vector Lines2;//端点四个,0->xmin,1->ymin,2->xmax,3->ymax
HoughLinesP(houghthre, Lines2, 1, CV_PI /180, 200, 100, 100);
vector vecPoint;
for(int i=0;isidewide)
sidewide = temp;
}
}
else if(ka == '-inf')//竖直横线
{
if(kj!='inf')
{
LineB = Lines2[j];
Point2f crossPoint;
crossPoint.x = LineA[0];
crossPoint.y = LineB[1];
vecPoint.push_back(crossPoint);
circle(houghthre, crossPoint, 3, Scalar(255, 255, 255));
}
else
{
LineB = Lines2[j];
double temp = qAbs(LineB[0]-LineA[0]);
if(temp>sidewide)
sidewide = temp;
}
}
}
}
这里输出了所有的交点,存储在vecPoint中。
4.求中心点
四个交点除以4!
float xSum = 0;
float ySum = 0;
for(int i=0;i
{
xSum = xSum+vecPoint[i].x;
ySum = ySum+vecPoint[i].y;
}
ui->xPoint->setText(QString::number(xSum/vecPoint.size()));
ui->yPoint->setText(QString::number(ySum/vecPoint.size()));
ui->side->setText(QString::number((sidewide)));
5.存在的问题
有的时候不能刚好识别出四个轮廓的,跟图片亮度和我设置的阈值有关,有的时候还识别不出来图片太暗的时候,因此我改善了一下,把阈值的手动设定,如图:
二值图做的精准,那识别就没问题了。下面这张测试用例阈值要设置成120才能识别两个边~
整体代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "qlabel.h"
using namespace cv;
using namespace std;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
cv::Mat image;
QImage img;
cv::Mat image_gray;
double sidewide;
int thresh = 45;
int max_thresh = 255;
private slots:
void on_select_clicked();
void on_detection_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PI 3.1415926
using namespace cv;
using namespace std;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_select_clicked()
{
QString filename = QFileDialog::getOpenFileName(this,tr("Open Image"),"",tr("Image File(*.bmp *.jpg *.jpeg *.png)"));
QTextCodec *code = QTextCodec::codecForName("gb18030");
std::string name = code->fromUnicode(filename).data();
image = cv::imread(name);
if(!image.data)
{
QMessageBox msgBox;
msgBox.setText(tr("image data is null"));
msgBox.exec();
}
}
void MainWindow::on_detection_clicked()
{
sidewide = 0;
Mat src;
src = image;
int k;
QString kStr = ui->k->text();
bool ok;
k = kStr.toInt(&ok,10);
cvtColor( src, image_gray, CV_BGR2GRAY );//灰度化
threshold(image_gray, src, k, 255, THRESH_TOZERO);
//imwrite("E:\\src.jpg",src);
GaussianBlur( src, src, Size(3,3), 0.1, 0, BORDER_DEFAULT );
blur( src, src, Size(3,3) ); //滤波
Canny( src, src, thresh, thresh*3, 3 );
Mat contours1;
Canny(src,contours1,125,350);
//Mat contoursInv;
//threshold(contours1,contoursInv,128,255,THRESH_BINARY_INV);
std::vector lines;
//调用hough函数
HoughLines(contours1,lines,1,PI/180,305);
threshold(image_gray, image_gray, 255, 255, THRESH_BINARY_INV);
imwrite("C:\\white3.jpg",image_gray);
//imshow("image_gray",image_gray);
Mat hough_output = imread("E:\\white3.jpg",CV_LOAD_IMAGE_COLOR);
//src.copyTo(hough_output);
std::vector::const_iterator it = lines.begin();
while(it != lines.end())
{
float rho = (*it)[0];
float theta=(*it)[1];
if(theta < PI/4. || theta > 3. *PI / 4.)
{
//接近于垂直线条
Point pt1(rho/cos(theta),0);
Point pt2((rho-hough_output.rows*sin(theta))/cos(theta),hough_output.rows);
line(hough_output,pt1,pt2,Scalar(255),1);
}
else
{
//接近于水平线 r=xcos(theta)+ysin(theta)
Point pt1(0,rho/sin(theta));
Point pt2(hough_output.cols,(rho-hough_output.cols*cos(theta))/sin(theta));
line(hough_output,pt1,pt2,Scalar(255),1);
}
++it;
}
//imwrite("E:\\src.jpg",hough_output);
Mat houghGray;
Mat houghthre;
//Mat New = imread("E:\\result2.jpg");
cvtColor(hough_output, houghGray, CV_BGR2GRAY);
threshold(houghGray, houghthre, 100, 255, THRESH_BINARY);
Canny( houghthre, houghthre, thresh, thresh*3, 3 );
//imshow("erodeImg",houghthre);
vector Lines2;
HoughLinesP(houghthre, Lines2, 1, CV_PI /180, 200, 100, 100);
vector vecPoint;
for(int i=0;i
{
Vec4i LineA = Lines2[i];
Vec4i LineB;
for(int j=i+1;j
{
double ka = (double)(LineA[3] - LineA[1])/(double)(LineA[2] - LineA[0]);
double kb;
double kj = (double)(Lines2[j][3] - Lines2[j][1])/(double)(Lines2[j][2] - Lines2[j][0]);
if (kj*ka < 0)
{
LineB = Lines2[j];
kb = kj;
Point2f crossPoint;
crossPoint.x = (ka*LineA[0] - LineA[1] - kb*LineB[0] + LineB[1]) / (ka - kb);
crossPoint.y = (ka*kb*(LineA[0] - LineB[0]) + ka*LineB[1] - kb*LineA[1]) / (ka - kb);
vecPoint.push_back(crossPoint);
circle(houghthre, crossPoint, 5, Scalar(255, 255, 255), -1, 8, 0);
}
else if(ka==0)
{
if(kj!=0)
{
LineB = Lines2[j];
Point2f crossPoint;
crossPoint.x = LineB[0];
//cout<< crossPoint.x<
crossPoint.y = LineA[1];
vecPoint.push_back(crossPoint);
circle(houghthre, crossPoint, 5, Scalar(255, 255, 255));
}
else
{
LineB = Lines2[j];
double temp = qAbs(LineB[1]-LineA[1]);
if(temp>sidewide)
sidewide = temp;
}
}
else if(ka == '-inf')
{
if(kj!='inf')
{
LineB = Lines2[j];
Point2f crossPoint;
crossPoint.x = LineA[0];
crossPoint.y = LineB[1];
vecPoint.push_back(crossPoint);
circle(houghthre, crossPoint, 5, Scalar(255, 255, 255));
}
else
{
LineB = Lines2[j];
double temp = qAbs(LineB[0]-LineA[0]);
if(temp>sidewide)
sidewide = temp;
}
}
}
}
//imwrite("E:\\src.jpg",houghthre);
float xSum = 0;
float ySum = 0;
for(int i=0;i
{
xSum = xSum+vecPoint[i].x;
ySum = ySum+vecPoint[i].y;
}
ui->xPoint->setText(QString::number(xSum/vecPoint.size()));
ui->yPoint->setText(QString::number(ySum/vecPoint.size()));
ui->side->setText(QString::number((sidewide)));
}