opencv识别不规则图形:基于opencv图像识别的AI五子棋系列4
opencv识别不规则图形:基于opencv图像识别的AI五子棋系列4Mat getPerspectiveTransform(const Point2f src[] const Point2f dst[])输入原始图像和变换之后的图像对应4个点,便可以得到变换矩阵。之后用求解得到的矩阵输入perspectiveTransform便可以对一组点进行变换:求解变换公式的函数:透视变换后这里实现主要利用了透视变换的原理。透视变换(perspective transformation)是将图像投影到一个新的视平面,也称为投影映射
基于opencv图像识别的AI五子棋系列4—棋盘透视变换该篇文章是基于opencv图像识别的AI五子棋系列的第四篇文章。通过该系列文章,你可以进阶opencv图像识别,熟悉图像识别与处理的大致流程,熟悉AI智能......
上一篇文章对五子棋棋盘轮廓的四个角点进行了寻找,该篇文章,则在上一篇文章的基础上进行通过棋盘四个顶点进行透视变换。
首先看一下处理的效果图。

四个顶点

透视变换后
实现思路这里实现主要利用了透视变换的原理。
透视变换(perspective transformation)是将图像投影到一个新的视平面,也称为投影映射
求解变换公式的函数:
Mat getPerspectiveTransform(const Point2f src[]  const Point2f dst[])
    
输入原始图像和变换之后的图像对应4个点,便可以得到变换矩阵。之后用求解得到的矩阵输入perspectiveTransform便可以对一组点进行变换:
void perspectiveTransform(InputArray src  OutputArray dst  InputArray m)
    
注意这里src和dst的输入并不是图像,而是图像对应的坐标。
详细参数参见官方文档:
warpPerspectivehttps://docs.opencv.org/2.4.9/modules/imgproc/doc/geometric_transformations.html#warpperspective
getPerspectiveTransformhttps://docs.opencv.org/2.4.9/modules/imgproc/doc/geometric_transformations.html#getperspectivetransform
透视变换示例代码下面我们通过几幅图片来直观地感受一下透视变换的作用和效果。

原图:一张倾斜拍摄的照片

透视变换处理过程

俯瞰图:透视变换结果图
通过上面三幅图,我们应该知道了透视变换的作用,下面我贴出该示例代码,供大家学习和理解。
//透视变换,检测四边形,有时候容易检测不出
//但目前效果最好的就是该程序
#include "stdafx.h"
#include "core/core.hpp"  
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <iostream>  
#include <set>
cv::Point2f center(0  0);
//求四个顶点的坐标 
// 类函数,Point2f为一个类对象
cv::Point2f computeIntersect(cv::Vec4i a  cv::Vec4i b)
{
    int x1 = a[0]  y1 = a[1]  x2 = a[2]  y2 = a[3]  x3 = b[0]  y3 = b[1]  x4 = b[2]  y4 = b[3];
    if (float d = ((float)(x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)))
    {
        cv::Point2f pt;
        pt.x = ((x1*y2 - y1 * x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3 * x4)) / d;
        pt.y = ((x1*y2 - y1 * x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3 * x4)) / d;
        return pt;
    }
    else
        return cv::Point2f(-1  -1);
}
//确定四个点的中心线
void sortCorners(std::vector<cv::Point2f>& corners  cv::Point2f center)
{
    std::vector<cv::Point2f> top  bot;
    for (unsigned int i = 0; i< corners.size(); i  )
    {
        if (corners[i].y<center.y)
        {
            top.push_back(corners[i]);
        }
        else
        {
            bot.push_back(corners[i]);
        }
    }
    cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
    cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
    cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
    cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];
    corners.clear();
    //注意以下存放顺序是顺时针,当时这里出错了,如果想任意顺序下文开辟的四边形矩阵注意对应  
    corners.push_back(tl);
    corners.push_back(tr);
    corners.push_back(br);
    corners.push_back(bl);
}
// 算法流程:先彩色转灰度,然后模糊求canny边缘,再用hough检测直线,
// 求出四线交点,利用opencv自带的求透视矩阵的函数求出透视矩阵,
// 然后利用透视矩阵转换源图像所需的四边形,效果图后续见图片:
int main()
{
    cv::Mat src = cv::imread("4.jpg");
    if (src.empty())
    {
        return -1;
    }
    cv::Mat bw;
    //彩色转灰度
    cv::cvtColor(src  bw  CV_BGR2GRAY);
    cv::namedWindow("gray_src" 0);
    imshow("gray_src"  bw);
    //模糊
    cv::blur(bw  bw  cv::Size(3  3));
    cv::namedWindow("blur"  0);
    imshow("blur"  bw);
    //边缘检测
    cv::Canny(bw  bw  100  100  3);
    cv::namedWindow("cannyblur"  0);
    imshow("cannyblur"  bw);
    //hough检测直线
    std::vector<cv::Vec4i> lines;
    cv::HoughLinesP(bw  lines  1  CV_PI / 180  70  30  10);
    //1像素分辨能力  1度的角度分辨能力        >70可以检测成连线       30是最小线长  
    //在直线L上的点(且点与点之间距离小于maxLineGap=10的)连成线段,然后这些点全部删除,并且记录该线段的参数,就是起始点和终止点  
    //needed for visualization only//这里是将检测的线调整到延长至全屏,即射线的效果,其实可以不必这么做  
    for (unsigned int i = 0; i<lines.size(); i  )
    {
        cv::Vec4i v = lines[i];
        lines[i][0] = 0;
        lines[i][1] = ((float)v[1] - v[3]) / (v[0] - v[2])* -v[0]   v[1];
        lines[i][2] = src.cols;
        lines[i][3] = ((float)v[1] - v[3]) / (v[0] - v[2])*(src.cols - v[2])   v[3];
    }
    std::vector<cv::Point2f> corners;//线的交点存储  
    for (unsigned int i = 0; i<lines.size(); i  )
    {
        for (unsigned int j = i   1; j<lines.size(); j  )
        {
            cv::Point2f pt = computeIntersect(lines[i]  lines[j]);
            if (pt.x >= 0 && pt.y >= 0)
            {
                corners.push_back(pt);
            }
        }
    }
    std::vector<cv::Point2f> approx;
    cv::approxPolyDP(cv::Mat(corners)  approx  cv::arcLength(cv::Mat(corners)  true)*0.02  true);
    //检测是否是四边形,很多图片检测不到
    if (approx.size() != 4)
    {
        std::cout << "The object is not quadrilateral(四边形)!" << std::endl;
        return -1;
    }
    //get mass center  
    for (unsigned int i = 0; i < corners.size(); i  )
    {
        center  = corners[i];
    }
    center *= (1. / corners.size());
    sortCorners(corners  center);
    cv::Mat dst = src.clone();
    //Draw Lines  
    for (unsigned int i = 0; i<lines.size(); i  )
    {
        cv::Vec4i v = lines[i];
        cv::line(dst  cv::Point(v[0]  v[1])  cv::Point(v[2]  v[3])  CV_RGB(0  255  0));    //目标版块画绿线   
    }
    //draw corner points  
    cv::circle(dst  corners[0]  3  CV_RGB(255  0  0)  2);
    cv::circle(dst  corners[1]  3  CV_RGB(0  255  0)  2);
    cv::circle(dst  corners[2]  3  CV_RGB(0  0  255)  2);
    cv::circle(dst  corners[3]  3  CV_RGB(255  255  255)  2);
    //draw mass center  
    cv::circle(dst  center  3  CV_RGB(255  255  0)  2);
    cv::Mat quad = cv::Mat::zeros(300  220  CV_8UC3);//设定校正过的图片从320*240变为300*220  
                                                     //corners of the destination image  
    std::vector<cv::Point2f> quad_pts;
    quad_pts.push_back(cv::Point2f(0  0));
    quad_pts.push_back(cv::Point2f(quad.cols  0));//(220 0)  
    quad_pts.push_back(cv::Point2f(quad.cols  quad.rows));//(220 300)  
    quad_pts.push_back(cv::Point2f(0  quad.rows));
    // Get transformation matrix  
    cv::Mat transmtx = cv::getPerspectiveTransform(corners  quad_pts);   //求源坐标系(已畸变的)与目标坐标系的转换矩阵  
                                                                         // Apply perspective transformation透视转换  
    cv::warpPerspective(src  quad  transmtx  quad.size());
    cv::namedWindow("src"  0);
    cv::imshow("src"  src);
    cv::namedWindow("image"  0);
    cv::imshow("image"  dst);
    cv::namedWindow("quadrilateral"  0);
    cv::imshow("quadrilateral"  quad);
    cv::waitKey();
    return 0;
}最终代码
    
理解了上面透视变换的方法后我们就可以将五子棋盘进行透视变换了。
/*****************************************************************************************
五子棋棋盘棋子识别检测
1、灰度化,二值化
2、查找棋盘最外边轮廓
3、找到棋盘四个顶点
4、将棋盘透视变换成正投影
*****************************************************************************************/
#include<opencv2/opencv.hpp>
#include <iostream> 
#include <fstream>  
#include <stdlib.h> //srand()和rand()函数 
#include <time.h> //time()函数 
#include <opencv2/core/core.hpp> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/objdetect/objdetect.hpp> 
#include <opencv2/ml/ml.hpp>  
#include<opencv2\opencv.hpp>
#include <opencv2\imgproc\types_c.h>
#include<windows.h>
#define PI 3.1415926
#define  ROTATED_SIZE   600 //透视变换后的表盘图像大小
#define  CUT_SIZE       0 //透视变换时四周裁剪长度
using namespace std;
using namespace cv;
using namespace ml;
struct Index_Point
{//带引索的点
	Point point;
	int index;
};
/*******************************************************************************************
*函数功能 : 找每个轮廓的中心坐标
*输入参数 :轮廓或者凸包
*返 回 值 : 点向量
*编写时间 : 2018.8.9
*作    者 : diyun
********************************************************************************************/
vector< Point2f> find_lunkuo_zhongxin(vector<vector<Point>> dangban_RectContours)
{
	vector<Point2f> zhongxin_zuobiao;
	/// 计算矩
	vector<Moments> mu(dangban_RectContours.size());
	for (int i = 0; i < dangban_RectContours.size(); i  )
	{
		mu[i] = moments(dangban_RectContours[i]  false);
	}
	///  计算中心矩:
	vector<Point2f> mc(dangban_RectContours.size());
	for (int i = 0; i < dangban_RectContours.size(); i  )
	{
		mc[i] = Point2f(mu[i].m10 / mu[i].m00  mu[i].m01 / mu[i].m00);
		zhongxin_zuobiao.push_back(mc[i]);
	}
	return	zhongxin_zuobiao;
}
/***** 求两点间距离*****/
float getDistance(Point pointO  Point pointA)
{
	float distance;
	distance = powf((pointO.x - pointA.x)  2)   powf((pointO.y - pointA.y)  2);
	distance = sqrtf(distance);
	return distance;
}
//两点之间距离
float getDistance_1(Point pointO  Point pointA)
{
	float distance;
	distance = powf((pointO.x - pointA.x)  2)   powf((pointO.y - pointA.y)  2);
	distance = sqrtf(distance);
	return distance;
}
/***** 点到直线的距离:P到AB的距离*****/
float getDist_P2L(Point pointP  Point pointA  Point pointB)
{
	//求直线方程
	int A = 0  B = 0  C = 0;
	A = pointA.y - pointB.y;
	B = pointB.x - pointA.x;
	C = pointA.x*pointB.y - pointA.y*pointB.x;
	//代入点到直线距离公式
	float distance = 0;
	distance = ((float)abs(A*pointP.x   B*pointP.y   C)) / ((float)sqrtf(A*A   B*B));
	return distance;
}
//辅助函数:
/***** 输入角点组的前3个值,返回按Index由大到小排好的角点组*****/
vector<Index_Point> ListConnor_0(vector<Index_Point> Point_list)
{
	int i  j;
	vector<Index_Point> Point_Ordered(Point_list.size());
	for (j = 0; j < Point_Ordered.size() - 1; j  )
	{
		if (j == 0)
		{
			for (i = 0; i < Point_list.size(); i  )
			{
				if (Point_list[i].index>Point_Ordered[j].index)
				{
					Point_Ordered[j].index = Point_list[i].index;
					Point_Ordered[j].point = Point_list[i].point;
				}
			}
		}
		else
		{
			for (i = 0; i < Point_list.size(); i  )
			{
				if (Point_list[i].index<Point_Ordered[j - 1].index&&Point_list[i].index>Point_Ordered[j].index)
				{
					Point_Ordered[j].index = Point_list[i].index;
					Point_Ordered[j].point = Point_list[i].point;
				}
			}
		}
		if (Point_list[j].index == 0)
		{
			Point_Ordered[2].index = Point_list[j].index;
			Point_Ordered[2].point = Point_list[j].point;
		}
	}
	return Point_Ordered;
}
//辅助函数:
/***** 输入角点组,返回按Index由大到小排好的角点组*****/
vector<Index_Point> ListConnor(vector<Index_Point> Point_list)
{
	int i  j;
	vector<Index_Point> Point_Ordered(Point_list.size());
	for (j = 0; j < Point_Ordered.size(); j  )
	{
		if (j == 0)
		{
			for (i = 0; i < Point_list.size(); i  )
			{
				if (Point_list[i].index>Point_Ordered[j].index)
				{
					Point_Ordered[j].index = Point_list[i].index;
					Point_Ordered[j].point = Point_list[i].point;
				}
			}
		}
		else
		{
			for (i = 0; i < Point_list.size(); i  )
			{
				if (Point_list[i].index<Point_Ordered[j - 1].index&&Point_list[i].index>Point_Ordered[j].index)
				{
					Point_Ordered[j].index = Point_list[i].index;
					Point_Ordered[j].point = Point_list[i].point;
				}
			}
		}
		if (Point_list[j].index == 0)
		{
			Point_Ordered[3].index = Point_list[j].index;
			Point_Ordered[3].point = Point_list[j].point;
		}
	}
	return Point_Ordered;
}
/***** 2、输入单个轮廓,返回逆时针排序的4个角点*****/
vector<Index_Point> FindConnor(vector<Point> RectContours)
{
	///**(1).根据距离条件找出三个角点
	float distance = 0  distanceMax = 0;
	vector<Index_Point> connorPoint(4)  connor_order(4);
	int i = 0;
	distance = 0;
	distanceMax = 0;
	for (i = 0; i < RectContours.size(); i  )
	{//找第一个角点
		distance = getDistance(RectContours[i]  RectContours[0]);
		if (distance>distanceMax)
		{
			distanceMax = distance;
			connorPoint[0].point = RectContours[i];
			connorPoint[0].index = i;
		}
	}
	distance = 0;
	distanceMax = 0;
	for (i = 0; i < RectContours.size(); i  )
	{//找第二个角点
		distance = getDistance(RectContours[i]  connorPoint[0].point);
		if (distance>distanceMax)
		{
			distanceMax = distance;
			connorPoint[1].point = RectContours[i];
			connorPoint[1].index = i;
		}
	}
	distance = 0;
	distanceMax = 0;
	for (i = 0; i < RectContours.size(); i  )
	{//找第三个角点
		distance = getDistance(RectContours[i]  connorPoint[0].point)   getDistance(RectContours[i]  connorPoint[1].point);
		if (distance>distanceMax)
		{
			distanceMax = distance;
			connorPoint[2].point = RectContours[i];
			connorPoint[2].index = i;
		}
	}
	///**(2).对已经找到的角点排列
	connor_order = ListConnor_0(connorPoint);//
	connorPoint = connor_order;
	///**(3).找出3个怀疑是第四角点的点
	vector<Index_Point> connor4_Doubt(3);
	distance = 0;
	distanceMax = 0;
	for (i = connorPoint[1].index; i < connorPoint[0].index; i  )
	{//1 0号角点之间找到怀疑是4角点的点
		distance = getDistance(RectContours[i]  connorPoint[0].point)   getDistance(RectContours[i]  connorPoint[1].point);
		if (distance>distanceMax)
		{
			distanceMax = distance;
			connor4_Doubt[0].point = RectContours[i];
			connor4_Doubt[0].index = i;
		}
	}
	distance = 0;
	distanceMax = 0;
	for (i = connorPoint[2].index; i < connorPoint[1].index; i  )
	{//2 1号角点之间找到怀疑是4角点的点
		distance = getDistance(RectContours[i]  connorPoint[2].point)   getDistance(RectContours[i]  connorPoint[1].point);
		if (distance>distanceMax)
		{
			distanceMax = distance;
			connor4_Doubt[1].point = RectContours[i];
			connor4_Doubt[1].index = i;
		}
	}
	distance = 0;
	distanceMax = 0;
	for (i = connorPoint[0].index; i < RectContours.size()   connorPoint[2].index; i  )
	{//0 2号角点之间找到怀疑是4角点的点
		if (i< RectContours.size())
		{
			distance = getDistance(RectContours[i]  connorPoint[0].point)   getDistance(RectContours[i]  connorPoint[2].point);
			if (distance>distanceMax)
			{
				distanceMax = distance;
				connor4_Doubt[2].point = RectContours[i];
				connor4_Doubt[2].index = i;
			}
		}
		else
		{
			distance = getDistance(RectContours[i - RectContours.size()]  connorPoint[0].point)   getDistance(RectContours[i - RectContours.size()]  connorPoint[2].point);
			if (distance>distanceMax)
			{
				distanceMax = distance;
				connor4_Doubt[2].point = RectContours[i - RectContours.size()];
				connor4_Doubt[2].index = i;
			}
		}
	}
	///**(4).通过点到直线的距离找到第四个角点
	if (getDist_P2L(connor4_Doubt[0].point  connorPoint[0].point  connorPoint[1].point)>10)
	{
		connorPoint[3] = connor4_Doubt[0];
	}
	else if (getDist_P2L(connor4_Doubt[1].point  connorPoint[1].point  connorPoint[2].point)>10)
	{
		connorPoint[3] = connor4_Doubt[1];
	}
	else if (getDist_P2L(connor4_Doubt[2].point  connorPoint[0].point  connorPoint[2].point)>10)
	{
		connorPoint[3] = connor4_Doubt[2];
	}
	///**(5).对四个角点按顺时针排序
	connor_order = ListConnor(connorPoint);
	return connor_order;
}
/***** 3、输入逆时针排序的4个角点,把0号点赋给右下角那个 最后逆时针输出*****/
vector<Point> FindFirstPoint(vector<Index_Point> Point_list)
{
	int i  Ymax  Y;
	float Kl  Kr;
	vector<Index_Point> Connor_Point_Ready;
	for (i = 0; i < Point_list.size(); i  )
	{
		Point_list[i].index = i;
	}
	for (i = 0; i < Point_list.size(); i  )
	{//找到所有上一级斜比下一级小的角点
		if (i>0 && i<Point_list.size() - 1)
		{
			Kl = abs(((float)(Point_list[i].point.y - Point_list[i - 1].point.y)) / ((float)(Point_list[i].point.x - Point_list[i - 1].point.x)));
			Kr = abs(((float)(Point_list[i].point.y - Point_list[i   1].point.y)) / ((float)(Point_list[i].point.x - Point_list[i   1].point.x)));
			if (Kl<Kr)
			{
				Connor_Point_Ready.push_back(Point_list[i]);
			}
		}
		else if (i == 0)
		{
			Kl = abs(((float)(Point_list[i].point.y - Point_list[Point_list.size() - 1].point.y)) / ((float)(Point_list[i].point.x - Point_list[Point_list.size() - 1].point.x)));
			Kr = abs(((float)(Point_list[i].point.y - Point_list[i   1].point.y)) / ((float)(Point_list[i].point.x - Point_list[i   1].point.x)));
			if (Kl<Kr)
			{
				Connor_Point_Ready.push_back(Point_list[i]);
			}
		}
		else if (i == Point_list.size() - 1)
		{
			Kl = abs(((float)(Point_list[i].point.y - Point_list[i - 1].point.y)) / ((float)(Point_list[i].point.x - Point_list[i - 1].point.x)));
			Kr = abs(((float)(Point_list[i].point.y - Point_list[0].point.y)) / ((float)(Point_list[i].point.x - Point_list[0].point.x)));
			if (Kl<Kr)
			{
				Connor_Point_Ready.push_back(Point_list[i]);
			}
		}
	}
	Y = 0; Ymax = 0;
	Index_Point Point_First;
	Point_First.point = Point(0  0);
	Point_First.index = 0;
	for (i = 0; i < Connor_Point_Ready.size(); i  )
	{//以y最大的那个点作为first点
		if (Connor_Point_Ready[i].point.y >= Ymax)
		{
			Point_First.point = Connor_Point_Ready[i].point;
			Point_First.index = Connor_Point_Ready[i].index;
			Ymax = Connor_Point_Ready[i].point.y;
		}
	}
	vector<Point> PointOrder(Point_list.size());
	if (Point_list.size()>0)
	{
		for (i = 0; i < Point_list.size(); i  )
		{
			if (Point_First.index   i< Point_list.size())
			{
				PointOrder[i] = Point_list[Point_First.index   i].point;
			}
			else
			{
				PointOrder[i] = Point_list[Point_First.index   i - Point_list.size()].point;
			}
		}
	}
	return PointOrder;
}
//数字转换字符串
string num2str(int i)
{
	stringstream ss;
	ss << i;
	return ss.str();
}
/*输入原图和(一组)四个角点,返回透视变换后的图
方向dir: 0.从下往上看
1.从左往右看
2.从上往下看
3.从右往左看
*/
Mat RotateContours(Mat srcImg  Point* Connor  int dir)
{
	//找一个轮廓放入dstImg
	Mat dstImg;
	Mat drawing(srcImg.size()  CV_8UC1  cv::Scalar(0));
	vector<vector<Point>> RectContours;
	vector<Point> pts;
	pts.push_back(Connor[0]);
	pts.push_back(Connor[1]);
	pts.push_back(Connor[2]);
	pts.push_back(Connor[3]);
	RectContours.push_back(pts);
	drawContours(drawing  RectContours  0  Scalar(255  255  255)  -1);
	srcImg.copyTo(dstImg  drawing);//截取出需要的图片
								   //透视变换
	Point2f src_vertices[4];
	for (int i = 0; i < 4; i  )
	{
		src_vertices[i].x = pts[i].x;
		src_vertices[i].y = pts[i].y;
	}
	Point2f dst_vertices[4]  Targer[4];
	Targer[0] = Point(0  0);
	Targer[1] = Point(ROTATED_SIZE  0);
	Targer[2] = Point(ROTATED_SIZE  ROTATED_SIZE);
	Targer[3] = Point(0  ROTATED_SIZE);
	for (int i = 0; i < 4; i  )
	{
		if (i   dir<4)
		{
			dst_vertices[i] = Targer[i   dir];
		}
		else
		{
			dst_vertices[i] = Targer[i   dir - 4];
		}
	}
	Mat warpMatrix = getPerspectiveTransform(src_vertices  dst_vertices);
	Mat rotated;
	warpPerspective(dstImg  rotated  warpMatrix  Size(ROTATED_SIZE  ROTATED_SIZE)  INTER_LINEAR  BORDER_CONSTANT);
	rotated = rotated(Range(CUT_SIZE  ROTATED_SIZE - CUT_SIZE)  Range(CUT_SIZE  ROTATED_SIZE - CUT_SIZE));
	//测试
	//imshow("透视变换"  rotated);
	return rotated;
}
//5、输入原图及所有角点,返回所有透视变换后的图像,用于进行仪表识别
vector<Mat> Find_rotated_all(Mat srcImg  vector<vector<Point>>AllConnor)
{
	vector<Mat> rotated_all;
	Mat rotated;
	for (int i = 0; i < AllConnor[0].size(); i  )
	{
		Point one_ConnorGroup[4];
		for (int j = 0; j < 4; j  )
		{//为单独一组角点赋值
			one_ConnorGroup[j] = AllConnor[j][i];
		}
		rotated = RotateContours(srcImg  one_ConnorGroup  0);
		//测试
		//namedWindow("透视变换"  0);
		//imshow("透视变换"  rotated);
		//waitKey(2);
	}
	rotated_all.push_back(rotated);
	return rotated_all;
}
int main()
{
	float ret = 0;
	Mat srcImage0 = imread("1.jpg");//读取图片 
									//Mat srcImage0 = imread("app/1.jpg");//读取图片 
	if (srcImage0.empty())
	{
		cout << " 待预测图像不存在: " << endl;
		printf("[ALG ERROR][函数:%s][行号:%d] 图片未正常读取,请检查输入路径十分正确 \n"  __FUNCTION__  __LINE__  1);
		cout << " 待预测图像不存在: " << endl;
	}
	Mat srcImage  srcImage1;
	resize(srcImage0  srcImage0  Size(1920  1080));
	cvtColor(srcImage0  srcImage1  CV_BGR2GRAY);
	namedWindow("灰度化"  0);
	imshow("灰度化"  srcImage1);
	waitKey(2);
	srcImage = srcImage1 > 150; // 二值化
	namedWindow("二值化"  0);
	imshow("二值化"  srcImage);
	waitKey(2);
	/*****1、输入二值化图像返回有用的轮廓*****/
	/*一般通过长度滤除剩下两个,选择内部那个*/
	vector<vector<Point>> contours  RectContours  RectContours_fainal;
	int height = srcImage.rows;
	findContours(srcImage  contours  CV_RETR_LIST  CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
	vector<vector<Point>> hull(contours.size());//用于存放凸包
	vector<float> length(contours.size());
	vector<float> Area_contours(contours.size())  Area_hull(contours.size())  Rectangularity(contours.size())  circularity(contours.size());
	Mat drawing(srcImage.size()  CV_8UC3  cv::Scalar(255  255  255));
	for (int i = 0; i < contours.size(); i  )
	{//历遍所有的轮廓
		length[i] = arcLength(contours[i]  true);
		if (length[i] >2000 && length[i] <12000)
		{//通过长度匹配滤除小轮廓
			convexHull(Mat(contours[i])  hull[i]  false);//把凸包找出来
			Area_contours[i] = contourArea(contours[i]);
			Area_hull[i] = contourArea(hull[i]);
			Rectangularity[i] = Area_contours[i] / Area_hull[i];
			printf("过滤后轮廓长度为 length=_\n"  length[i]);
			circularity[i] = (4 * 3.1415*Area_contours[i]) / (length[i] * length[i]);
			//drawContours(drawing  contours  i  (255  0  0)  2);//得到方框
			//if (Rectangularity[i]>0.9&&circularity[i]<0.8&&Area_hull[i]>8000 && Area_hull[i]<50000)
			{//通过凸包面积滤除不对的凸包,找到最终的四边形
				RectContours.push_back(hull[i]);//把提取出来的方框导入到新的轮廓组
				drawContours(drawing  hull  i  Scalar(0  0  0)  1);//得到方框
			}
		}
	}
	if (0 == RectContours.size())
	{
		printf("[ALG ERROR][函数:%s][行号:%d] RectContours.size=0 当前帧未找到轮廓,检查仪表二值化是否正常\n"  __FUNCTION__  __LINE__);
	}
	/*识别结果显示,调试用*/
	//namedWindow("找轮廓结果"  0);
	//imshow("找轮廓结果"  drawing);
	//waitKey(2);
	/*将多个轮廓,弄成一个*/
	cvtColor(drawing  drawing  CV_BGR2GRAY);
	drawing = drawing<150; // 二值化
						   //imshow("二值化"  drawing);
						   //将线连接起来
	vector<Point2f> zhongxin_zuobiao;
	zhongxin_zuobiao = find_lunkuo_zhongxin(RectContours);
	if (zhongxin_zuobiao.size() >= 2)
	{
		for (int i = 0; i < zhongxin_zuobiao.size() - 1; i  )
		{
			for (int j = i   1; j < zhongxin_zuobiao.size(); j  )
			{
				float dis_juxin = getDistance_1(zhongxin_zuobiao[i]  zhongxin_zuobiao[j]);
				//if (dis_juxin <65)
				{
					line(drawing  zhongxin_zuobiao[i]  zhongxin_zuobiao[j]  Scalar(0)  40  LINE_AA);
				}
			}
		}
	}
	vector<vector<Point>> contours1;
	findContours(drawing  contours1  CV_RETR_EXTERNAL  CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
	Mat drawing1(drawing.size()  CV_8UC3  cv::Scalar(255  255  255));
	vector<vector<Point>> hull1(contours1.size());//用于存放凸包
	for (int i = 0; i < contours1.size(); i  )
	{//历遍所有的轮廓
	 //drawContours(drawing1  contours1  i  Scalar(0  255  0)  1);//得到方框
		convexHull(Mat(contours1[i])  hull1[i]  false);//把凸包找出来
		RectContours_fainal.push_back(hull1[i]);//把提取出来的方框导入到新的轮廓组
		drawContours(drawing1  hull1  i  Scalar(0  0  0)  4);//得到方框
	}
	namedWindow("最终轮廓"  0);
	imshow("最终轮廓"  drawing1);
	vector<Index_Point> ConnorPoint(4);
	vector<Point> ConnorPoint_ordered(4);
	vector<vector<Point>> ConnorPoint_Output(4);
	int i;
	string filename;//裁剪出来的负样本图片文件名 
	for (i = 0; i < RectContours_fainal.size(); i  )
	{
		ConnorPoint = FindConnor(RectContours_fainal[i]);
		ConnorPoint_ordered = FindFirstPoint(ConnorPoint);
		ConnorPoint_Output[0].push_back(ConnorPoint_ordered[2]);
		ConnorPoint_Output[1].push_back(ConnorPoint_ordered[1]);
		ConnorPoint_Output[2].push_back(ConnorPoint_ordered[0]);
		ConnorPoint_Output[3].push_back(ConnorPoint_ordered[3]);
	}
	//ConnorPoint_Output[0][i]代表第i个轮廓的0号角点 顺时针输出
	//return ConnorPoint_Output;
	vector<Mat> rotated_all;
	if (ConnorPoint_Output[0].size()>0)
	{
		vector<vector<Point>> RectContours;
		vector<Point> pts;
		for (int i = 0; i < ConnorPoint_Output[0].size(); i  )
		{	
			//2、输入原图及所有角点,返回所有透视变换后的图像,用于进行仪表识别
			rotated_all = Find_rotated_all(srcImage0  ConnorPoint_Output);
			Point one_ConnorGroup[4];
			for (int j = 0; j < 4; j  )
			{//为单独一组角点赋值
				one_ConnorGroup[j] = ConnorPoint_Output[j][i];
				circle(srcImage0  one_ConnorGroup[j]  10  Scalar(0  255  0)  -1); //第五个参数我设为-1,表明这是个实点。
			}
			namedWindow("四个角点"  0);
			imshow("四个角点"  srcImage0);
			waitKey(2);
		}
	}
	else
	{
		//没找到,返回下帧继续找
		printf("[ALG ERROR][函数:%s][行号:%d] 当前帧未找到方形仪表四边形四个角点,返回,下帧继续找 \n"  __FUNCTION__  __LINE__);
		return ERROR;
	}
	printf("透视变换后的图像个数=%d \n"  rotated_all.size());
	if (rotated_all.size() == 0)
	{
		//没找到,返回下帧继续找
		printf("[ALG ERROR][函数:%s][行号:%d] 未找到透视变换后的图像 \n"  __FUNCTION__  __LINE__);
		return ERROR;
		return ERROR;
	}
	//namedWindow("test_img[]"  0);
	//imshow("test_img[]"  rotated_all[0]);
	filename = "finall_rotated_all_"   num2str(0)   ".jpg";
	imwrite(filename  rotated_all[0]);
	namedWindow("透视变换后棋盘"  0);
	imshow("透视变换后棋盘"  rotated_all[0]);
	waitKey(2);
	waitKey(2000);
	waitKey(2000);
	waitKey(0);
	return 0;
}
          




