这是一个来自网友的提问:
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164719254-401453228.bmp)
这是一张激光图片,需要得到3横3竖的交点,其中的圆和X是用了测试仿射变换的。
一、图片分析
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164502615-385676235.png)
大像素、单通道。
二、思路分析
必须在量化的基础上,寻找这9个焦点的“固有特征”;至于9个点之间的排序,那是简单问题。
三、算法实践
1、这样一个图像,单通道所以无法做HSV分解;必须考虑到光照影响,所以不能采用OSTU;经验告诉我,直接bin能够得到较好效果。
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164503058-1359026348.png)
2、得到的结果,可能会有孤立点,需要通过形态学方法去除
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164503372-630053410.png)
参考相关文章
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164503647-1312427510.png)
需要做的是”开“运算。根据观察,我们采用5*5的核心。
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164503876-945087297.png)
得到的结果是奇异点全部消失,但是现有轮廓会有所衰弱。
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164504157-1764430533.png)
3、进一步执行细化,精确定位;或者也会出现细微偏差
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164504351-428599863.png)
4、那么,这个图像质量,我们如何建立特征提取模型了?
对于这样一个点
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164504577-951754474.png)
4个象限,那么对于我们这些交点来说,肯定是有特征的,
比如上图表示,对于这幅图像中的所有的点,可以区分为1-4个象限;就目前这4个象限中来看,每个象限都存在有数据的区域。
但是经过编码发现,由于原图中有圆形等其他图像,干扰比较大,所以可以进一步增加约束为“寻找所有该点本身、其左边、右边、上边、下边都为255的点“
由于这是一个比较明显的特征,所以我们可以直接编写代码去提取。
//遍历图像,这里我在遍历的时候缩了一个像素
vector<Point> vecPoints;
for(int i=1;i<src.rows - 1;i++){
for(int j=1;j<src.cols - 1;j++){
if ( src.at<uchar>(i,j) == 255) //首先该像素要有数据
{
if (src.at<uchar>(i-1,j) == 255 && src.at<uchar>(i+1,j) == 255 && src.at<uchar>(i,j-1) == 255 && src.at<uchar>(i,j+1)==255)
{
vecPoints.push_back(Point(j,i));
}
}
}
}
//在底图上进行绘制
cvtColor(srcClone,srcClone,COLOR_GRAY2BGR);
for (int i = 0;i<vecPoints.size();i++)
{
circle(srcClone,vecPoints[i],10,Scalar(0,0,255),-1);
}并且在原图上进行绘图,得到以下结果
![](https://img2018.cnblogs.com/blog/508489/201901/508489-20190120164505000-61865159.jpg)
5、这个时候,我们要对得到的有限的点进行补全和排序
需要注意的是这几个点本身肯定是有位置关系的,如果基本符合横平竖直的条件的话,我们可以做投影。但是具体情况还是要根据项目原始需求进行。
//对得到的有限的点进行补全和排序
vector<int> vup;
vector<int> vdown;
projection2(srcBoard,vup,vdown,1);此外,对可能出现的异常情况,也应该有很好的控制。
附录:
其实在这个程序中,比较关键的,处理解体思路之外,应该就是细化函数(来自GOCVHELPER/GITHUB)
//将 DEPTH_8U型二值图像进行细化 经典的Zhang并行快速细化算法
//细化算法
void thin(const Mat &src, Mat &dst, const int iterations){
const int height =src.rows -1;
const int width =src.cols -1;
//拷贝一个数组给另一个数组
if(src.data != dst.data)
src.copyTo(dst);
int n = 0,i = 0,j = 0;
Mat tmpImg;
uchar *pU, *pC, *pD;
bool isFinished =FALSE;
for(n=0; n<iterations; n++){
dst.copyTo(tmpImg);
isFinished =FALSE; //一次 先行后列扫描 开始
//扫描过程一 开始
for(i=1; i<height; i++) {
pU = tmpImg.ptr<uchar>(i-1);
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i+1);
for(int j=1; j<width; j++){
if(pC[j] > 0){
int ap=0;
int p2 = (pU[j] >0);
int p3 = (pU[j+1] >0);
if (p2==0 && p3==1)
ap++;
int p4 = (pC[j+1] >0);
if(p3==0 && p4==1)
ap++;
int p5 = (pD[j+1] >0);
if(p4==0 && p5==1)
ap++;
int p6 = (pD[j] >0);
if(p5==0 && p6==1)
ap++;
int p7 = (pD[j-1] >0);
if(p6==0 && p7==1)
ap++;
int p8 = (pC[j-1] >0);
if(p7==0 && p8==1)
ap++;
int p9 = (pU[j-1] >0);
if(p8==0 && p9==1)
ap++;
if(p9==0 && p2==1)
ap++;
if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7){
if(ap==1){
if((p2*p4*p6==0)&&(p4*p6*p8==0)){
dst.ptr<uchar>(i)[j]=0;
isFinished =TRUE;
}
}
}
}
} //扫描过程一 结束
dst.copyTo(tmpImg);
//扫描过程二 开始
for(i=1; i<height; i++){
pU = tmpImg.ptr<uchar>(i-1);
pC = tmpImg.ptr<uchar>(i);
pD = tmpImg.ptr<uchar>(i+1);
for(int j=1; j<width; j++){
if(pC[j] > 0){
int ap=0;
int p2 = (pU[j] >0);
int p3 = (pU[j+1] >0);
if (p2==0 && p3==1)
ap++;
int p4 = (pC[j+1] >0);
if(p3==0 && p4==1)
ap++;
int p5 = (pD[j+1] >0);
if(p4==0 && p5==1)
ap++;
int p6 = (pD[j] >0);
if(p5==0 && p6==1)
ap++;
int p7 = (pD[j-1] >0);
if(p6==0 && p7==1)
ap++;
int p8 = (pC[j-1] >0);
if(p7==0 && p8==1)
ap++;
int p9 = (pU[j-1] >0);
if(p8==0 && p9==1)
ap++;
if(p9==0 && p2==1)
ap++;
if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7){
if(ap==1){
if((p2*p4*p8==0)&&(p2*p6*p8==0)){
dst.ptr<uchar>(i)[j]=0;
isFinished =TRUE;
}
}
}
}
}
} //一次 先行后列扫描完成
//如果在扫描过程中没有删除点,则提前退出
if(isFinished ==FALSE)
break;
}
}
}
// end of thin全部代码:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include "GOCVHelper.h"
using namespace cv;
using namespace std;
using namespace GO;
int main()
{
Mat src = imread("E:/sandbox/cross/1.bmp",0);
Mat srcClone = src.clone();
Mat srcBoard(src.size(),CV_8U,Scalar::all(0));
//阈值处理
threshold(src,src,100,255,THRESH_BINARY);
//形态学去噪音
Mat element = getStructuringElement(MORPH_RECT,Size(5,5));
morphologyEx(src,src,CV_MOP_OPEN,element);
//二值图像细化处理
GO::thin(src,src);
//遍历图像,这里我在遍历的时候缩了一个像素
vector<Point> vecPoints;
for(int i=1;i<src.rows - 1;i++){
for(int j=1;j<src.cols - 1;j++){
if ( src.at<uchar>(i,j) == 255) //首先该像素要有数据
{
if (src.at<uchar>(i-1,j) == 255 && src.at<uchar>(i+1,j) == 255 && src.at<uchar>(i,j-1) == 255 && src.at<uchar>(i,j+1)==255)
{
vecPoints.push_back(Point(j,i));
}
}
}
}
//在底图上进行绘制
cvtColor(srcClone,srcClone,COLOR_GRAY2BGR);
for (int i = 0;i<vecPoints.size();i++)
{
circle(srcClone,vecPoints[i],10,Scalar(0,0,255),-1);
circle(srcBoard,vecPoints[i],1,Scalar(255),-1);
}
//对得到的有限的点进行补全和排序
vector<int> vup;
vector<int> vdown;
projection2(srcBoard,vup,vdown,1);
waitKey();
return 0;
}