基于canny边缘检测的连通域检测算法

描述

在之前扫描二维码提取任务之后,工作中又需要将身份证图像中的身份证号码提取出来,然后给同事调用进行识别。之前的连通域检测算法比较“蛮力”,因为它一旦检测出一个大的区域,那么这区域中的所有内部区域都将不复存在了。所以在连通域检测时,需要第一步去掉周围可能存在的白边,否则就会失败。后来笔者换了一个思路,如果检测一个区域时保存对应生成该区域的点,该区域不符合要求的话就将这些点擦掉,从而就不会影响到内部的区域了。于是就有了一下算法的诞生:

(1)从左上角开始,从碰到的第一个白点开始探测最大的连通域,获取离该点小于max_dis的所有点,放到一个list中。

(2)然后遍历该列表,并将离每一个点距离小于max_dis的点都放到该list中。

(3)遍历结束后,计算包含list中所有点的最小rect区域。

(4)根据设定的目标区域特点,如长宽、长宽比等,来判断该区域是否满足要求,如果满足,则放到rectlist中。然后将该list中的所有点都置黑。转到(1)执行。

(5)如果rectlist为空,则没有获取到目标rect。如果>=1 则将之按照一个规则进行排序(应该是那个最主要的特征),然后输出最可能的那个rect。

算法过程演示如下:

原图:

色彩过滤(为了得到效果好一点的canny图):

canny图:

图像

检测画框与擦除:

第一次 画框:

图像

第一次擦除:

图像

第二次画框:

图像

第二次擦除

图像

第n次画框:

图像

第n次擦除:

图像

最后的什么都没剩下:

图像

得出结果:

详细算法代码如下:

FindIdCode.h

#include "opencv2/core/core.hpp"

#include "opencv2/imgproc/imgproc_c.h"

#include "opencv2/imgproc/imgproc.hpp"

#include "opencv2/highgui/highgui.hpp"

#include

#include < io.h> 

#include

#include

#include "opencv/cv.h"

#include "opencv/cxcore.h"

#include "opencv2/highgui/highgui_c.h"

#include "direct.h"

using namespace cv;

using namespace std;

class CGetIDCOde

{

public:

CGetIDCOde();

//删除文件 并返回string 值

string getFilePath( const char * szBuf);

//获取文件长度

long  GetFileLength(const char * filepath);

//过滤颜色

void FilterColor(string strImgFileName);

//找到目标连通域

RECT FindTargetConnectedDomain();

//将list中的点都设置成某一个颜色

void SetPointListColor(Mat & srcImg, std::vector pointList, int nColor);

//根据点列表获取最小包含区域

void GetRectFromPointList(std::vector& pointList, RECT & rtRect);

//获取与该点临近的点

void GetNearPoint(Mat & srcImg,cv::Point currentPoint, std::vector & pointList);

//将一个box框画成某一个颜色

void DrowBoxColor(Mat &srcImg, std::vector &boxList, int nColor);

//获取一个联通区域

BOOL GetOneConnectedDomain(Mat & srcImg, std::vector& pointList, RECT &rect);

//将图像的某一个区域保存为图像

void SavePicWithDestRect(string strSource, string strDest, RECT destRect);

//获取身份证号图像区域

RECT GetIdCode(const char * szSourceFile);

//边缘检测

int outLinePic2();

char szCurrentPath[MAX_PATH]; 

string strOrigin;

string strSave1;

string strSave1_1;

string strSave2;

string strSave3;

string strSave4;

string strSave5;

string strSave3_0;

string strSave3_1;

string strSave3_2;

string strSave3_3;

string strSave6;

string strSave7;

string strSave8;

};

CPP代码:

FindIdCode.cpp

#include "FindIdCode.h"

int   mMAX_DIS = 0;  

double fScale = 0.0;

#define  BOX_WIDTH  50

#define  BLACK  0

#define  MID_BLACK_WHITE 128

#define  WHITE 255

#define  RATE  0.2

//按照框的宽度排序 

BOOL SortByM5(RECT &v1, RECT &v2)

{

int nWidth1 = v1.right - v1.left;

int nHeight1 = v1.bottom - v1.top;

int nWidth2 = v2.right - v2.left;

int nHeight2 = v2.bottom - v2.top;

float fRate1 = 1.0 * nWidth1 / nHeight1;

float fRate2 = 1.0 * nWidth2 / nHeight2;

if (fRate1 > fRate2)

{

return TRUE;

}

else

{

return FALSE;

}

}

string CGetIDCOde::getFilePath( const char * szBuf)

{

string str;

str = szCurrentPath;

str += "\";

str += szBuf;

//删除已经存在的文件

DeleteFile(str.c_str());

return str;

}

long  CGetIDCOde::GetFileLength(const char * filepath)

{

FILE* file = fopen(filepath, "rb");  

if (file)  

{  

long size = filelength(fileno(file));  

return size;

else

{

return 0;

}

}

//颜色过滤

void CGetIDCOde::FilterColor(string strImgFileName)

{

uchar uDifferMax = 80;

uchar rMax = 100;

uchar bMax = 150;

uchar gMax = 150;

uchar uWhite = 255;

uchar r,b,g;

IplImage *workImg = cvLoadImage(strImgFileName.c_str(), CV_LOAD_IMAGE_UNCHANGED);

//像素太高的进行缩放

if (workImg->width > 900)

{

int nTargetWidth = 600;

fScale = 1.0 * workImg->width / nTargetWidth; 

CvSize czSize;

//计算目标图像大小  

czSize.width = nTargetWidth;

czSize.height = workImg->height / fScale; 

//IplImage *pSrcImage = cvLoadImage(strSave2.c_str(), CV_LOAD_IMAGE_UNCHANGED); 

IplImage *pDstImage = cvCreateImage(czSize, workImg->depth, workImg->nChannels); 

cvResize(workImg, pDstImage, CV_INTER_AREA); 

cvReleaseImage(&workImg); 

cvSaveImage(strSave1_1.c_str(),pDstImage);

workImg = pDstImage;

}

for(int x=0;xheight;x++)  

{  

for(int y=0;ywidth;y++)  

{  

b=((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+0];  

g=((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+1];  

r=((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+2];

//偏色比较严重的

//uchar uMax = max(max(b,g),r);

//uchar uMin = min(min(b,g),r);

//if ( uMax - uMin > uDifferMax)

int nAbove = 0;

if (b >= uDifferMax)

{

nAbove ++;

}

if (g >= uDifferMax)

{

nAbove ++;

}

if (r >= uDifferMax)

{

nAbove ++;

}

//有两个大于80 

if(nAbove >= 2 || b > bMax || g > gMax || r > rMax)

{

((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+0] = uWhite;

((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+1] = uWhite;

((uchar*)(workImg->imageData+x*workImg->widthStep))[y*3+2] = uWhite;

}

}  

}  

cvSaveImage(strSave1.c_str(), workImg);

}

int CGetIDCOde::outLinePic2()

{

Mat src = imread(strSave1.c_str());

Mat dst;

if (!src.empty())

{

//输入图像

//输出图像

//输入图像颜色通道数

//x方向阶数

//y方向阶数

Sobel(src,dst,src.depth(),1,1);

//imwrite("sobel.jpg",dst);

//输入图像

//输出图像

//输入图像颜色通道数

Laplacian(src,dst,src.depth());

imwrite("laplacian.jpg",dst);

//输入图像

//输出图像

//彩色转灰度

cvtColor(src,src,CV_BGR2GRAY);  //canny只处理灰度图

//输入图像

//输出图像

//低阈值

//高阈值,opencv建议是低阈值的3倍

//内部sobel滤波器大小

//threshold1和threshold2 当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。50 150

Canny(src,dst,220,240,3);

imwrite(strSave2.c_str(),dst);

return 0;

}

else

{

cout<< "IMG is not exist!";

return -1;

}

}

void CGetIDCOde::SetPointListColor(Mat & srcImg, std::vector pointList, int nColor)

{

for (int i = 0; i < pointList.size(); i ++)

{

int x = pointList[i].x;

int y = pointList[i].y;

*(srcImg.data + srcImg.step[0] * y + srcImg.step[1] * x) = nColor;

}

}

RECT CGetIDCOde::FindTargetConnectedDomain()

{

Mat srcImg = imread(strSave2.c_str(), CV_LOAD_IMAGE_GRAYSCALE);

//设定最大的距离

mMAX_DIS = srcImg.cols * (1.0 * 9 / 400) + 1;

int nMaxWidth = 0.6 * srcImg.cols;

int nMaxHeight = 1.0 * 5 * srcImg.rows / 36 ;

std::vector pointList;

//探测一个矩形连通域,判断是否符合目标特征,不符合删除找下一个。

//找到一个放入vector中。

std::vector targetRectList;

while(TRUE)

{

RECT rect;

GetOneConnectedDomain(srcImg, pointList,rect);

//判断该rect是否符合要求。

int nWidth = rect.right - rect.left;

int nHeight = rect.bottom - rect.top;

// 300 20 

float fRate = 1.0 * nWidth / nHeight;

if (nHeight > 5 && nHeight < nMaxHeight && nWidth > 100 && nWidth < nMaxWidth   &&  fRate > 8 && fRate < 20)

{

//SavePicWithDestRect(strOrigin, strSave8, rect);

targetRectList.push_back(rect);

//break;

}

else

{

if (pointList.empty())

{

break;

}

}

//置黑然后找下一个

SetPointListColor(srcImg, pointList, BLACK);

imwrite(strSave3_3.c_str(),srcImg);

pointList.clear();

}

//有多个排序

if (targetRectList.size() > 0)

{

sort(targetRectList.begin(), targetRectList.end(), SortByM5);

//找到 提取图像 保存。

RECT rect = targetRectList[0];

rect.left -= mMAX_DIS;

if (rect.left < 0)

{

rect.left = 0;

}

rect.top -= mMAX_DIS;

if (rect.top < 0)

{

rect.top = 0;

}

rect.right += mMAX_DIS;

if (rect.right > srcImg.cols)

{

rect.right = srcImg.cols;

}

rect.bottom += mMAX_DIS;

if (rect.bottom > srcImg.rows)

{

rect.bottom = srcImg.rows;

}

if (fScale > 0.0)

{

rect.left *= fScale;

rect.right*= fScale;

rect.bottom *= fScale;

rect.top *= fScale;

}

return rect;

//SavePicWithDestRect(strOrigin, strSave8, rect);

}

else

{

//cout<< "find no numbers!";

//getchar();

RECT rect;

rect.bottom = rect.top = rect.left = rect.right = 0;

return rect;

}

}

//保存图像

void CGetIDCOde::SavePicWithDestRect(string strSource, string strDest, RECT destRect)

{

IplImage* src;  

IplImage* dst;

src = cvLoadImage(strSource.c_str(),1);  

if(!src)  

{

return ;

}

cvSetImageROI(src,cvRect(destRect.left,destRect.top ,destRect.right - destRect.left, destRect.bottom - destRect.top));  

dst = cvCreateImage(cvSize(destRect.right - destRect.left, destRect.bottom - destRect.top),  

IPL_DEPTH_8U,  

src->nChannels);  

cvCopy(src,dst,0);  

cvResetImageROI(src);   

cvSaveImage(strDest.c_str(), dst);

cvReleaseImage(&dst);

cvReleaseImage(&src);

}

BOOL CGetIDCOde::GetOneConnectedDomain(Mat & srcImg, std::vector& pointList, RECT &rect)

{

int nWidth = srcImg.cols;

int nHeight = srcImg.rows;

int nXStart = 0;

int nYStart = 0;

BOOL bBlack = TRUE;

BOOL bBreak = FALSE;

int nWhite = 0;

//找到第一个最上角的白点

for (int y = 0; y < nHeight; y ++)

{

for (int x = 0; x < nWidth; x++)

{

int nPixel = (int)(*(srcImg.data + srcImg.step[0] * y + srcImg.step[1] * x));

if (nPixel > MID_BLACK_WHITE)

{

nXStart = x;

nYStart = y;

cv::Point tempPint(nXStart,nYStart);

pointList.push_back(tempPint);

bBreak = TRUE;

break;

}

}

if (bBreak)

{

break;

}

}

int nSize = pointList.size();

//探测下一个点。

for (int i = 0; i < nSize; i ++)

{

cv::Point currentPoint = pointList[i];

GetNearPoint(srcImg, currentPoint, pointList);

nSize = pointList.size();

//如果超过4000个点则删除后重新再来

if (nSize > 3000)

{

break;

}

}

//对该pointList求最小包含的矩形框。

GetRectFromPointList(pointList, rect);

std::vector tempTect;

tempTect.push_back(rect);

DrowBoxColor(srcImg,tempTect, WHITE);

imwrite(strSave3_2.c_str(),srcImg);

DrowBoxColor(srcImg,tempTect, BLACK);

return TRUE;

}

void CGetIDCOde::GetRectFromPointList(std::vector& pointList, RECT & rtRect)

{

int nLeft = 0;

int nTop = 0;

int nRight = 0;

int nBottom = 0;

for(int i = 0; i < pointList.size(); i ++)

{

cv::Point tempPoint = pointList[i];

if (i == 0)

{

nLeft = nRight = tempPoint.x;

nTop = nBottom = tempPoint.y;

}

else

{

if (tempPoint.x < nLeft)

{

nLeft = tempPoint.x;

}

if (tempPoint.x > nRight)

{

nRight = tempPoint.x;

}

if (tempPoint.y < nTop)

{

nTop = tempPoint.y;

}

if (tempPoint.y > nBottom)

{

nBottom = tempPoint.y;

}

}

}

rtRect.left = nLeft;

rtRect.top = nTop;

rtRect.right = nRight;

rtRect.bottom = nBottom;

}

void CGetIDCOde::GetNearPoint(Mat & srcImg,cv::Point currentPoint, std::vector & pointList)

{

//探测以该点为中心的 20 * 20范围的点。

for (int y = max(0, currentPoint.y - mMAX_DIS); y < min(srcImg.rows, currentPoint.y + mMAX_DIS); y ++)

{

for (int x = max(currentPoint.x - mMAX_DIS, 0); x < min(srcImg.cols, currentPoint.x + mMAX_DIS); x ++)

{

int nPixel = (int)(*(srcImg.data + srcImg.step[0] * y + srcImg.step[1] * x));

if (nPixel > MID_BLACK_WHITE)

{

cv::Point tempPint(x, y);

//看该点是否已经放入list

std::vector::iterator itFind =  find( pointList.begin(), pointList.end(),tempPint);

if (itFind == pointList.end())

{

pointList.push_back(tempPint);

}

}

}

}

}

//画框线为一个颜色

void CGetIDCOde::DrowBoxColor(Mat &srcImg, std::vector &boxList, int nColor)

{

int nResultSize = boxList.size();

for (int i = 0; i < nResultSize; i ++)

{

RECT tempRect = boxList[i];

//上下边线

int y1 = tempRect.top;

int y2 = tempRect.bottom;

for (int x = tempRect.left;  x <= tempRect.right; x ++)

{

*(srcImg.data + srcImg.step[1] * x + srcImg.step[0] * y1) = nColor;

*(srcImg.data + srcImg.step[1] * x + srcImg.step[0] * y2) = nColor;

}

//左右边线

int x1 = tempRect.left;

int x2 = tempRect.right;

for (int y = tempRect.top; y <= tempRect.bottom; y ++)

{

*(srcImg.data + srcImg.step[1] * x1 + srcImg.step[0] * y) = nColor;

*(srcImg.data + srcImg.step[1] * x2 + srcImg.step[0] * y) = nColor;

}

}

}

RECT CGetIDCOde::GetIdCode(const char * szSourceFile)

{

CopyFile(szSourceFile, strOrigin.c_str(), FALSE);

//文件大小 过小则不进行图像过滤

RECT rect;

rect.bottom = rect.top = rect.left = rect.right = 0;

long nFileLen = GetFileLength(strOrigin.c_str());

if (nFileLen == 0)

{

return rect;

}

else if (nFileLen > 7000 )

{

FilterColor(strOrigin);

}

else

{

CopyFile(strOrigin.c_str(), strSave1.c_str(),FALSE );

}

if (outLinePic2() == -1)

{

return rect;

}

return FindTargetConnectedDomain();

}

CGetIDCOde::CGetIDCOde()

{

_getcwd(szCurrentPath,MAX_PATH);

strOrigin = getFilePath("imageText.jpg");

strSave1 = getFilePath("imageText_D.jpg");

strSave1_1 = getFilePath("imageText_ReSize.jpg");

strSave2 = getFilePath("canny.jpg");

strSave3 = getFilePath("imageText_Clear0.jpg");

strSave4 = getFilePath("imageText_Clear1.jpg");

strSave5 = getFilePath("imageText_Clear2.jpg");

strSave3_0 = getFilePath("imageText_Clear3_0.jpg");

strSave3_1 = getFilePath("imageText_Clear3_1.jpg");

strSave3_2 = getFilePath("imageText_Clear3_2.jpg");

strSave3_3 = getFilePath("imageText_Clear3_3.jpg");

strSave6 = getFilePath("imageText_Clear3.jpg");

strSave7 = getFilePath("imageText_D.jpg");

strSave8 = getFilePath("imageText_Clear4.jpg");

}

类的测试代码:

#include "../FindIdCode/FindIdCode.h"

using namespace std;

#ifdef _DEBUG

#pragma comment(lib, "Debug/FindIdCode.lib")

#else

#pragma comment(lib, "Release/FindIdCode.lib")

#endif

int main(int argc, char **argv)

{

if(argc < 2) 

return(1);

CGetIDCOde getIdcode;

//char* szSourceFile = "D:\scan\00000000000000000\3032_024.jpg";

//dll测试

char* szSourceFile = argv[1];

RECT rect = getIdcode.GetIdCode(szSourceFile);

//CopyFile(szSourceFile,strOrigin.c_str(), FALSE);

getIdcode.SavePicWithDestRect(szSourceFile, getIdcode.strSave8, rect);

cout<<"the rect is "<

return 0;

}

说明:

由于不断的进行循环检测,如果像素过高图片太大则耗时较多,而且边缘检测效果特别不好,所以程序中对于像素宽度大于900的则缩放到400。

程序运行效果的好坏直接影响因数是 canny图片的效果。所以对于不同特点的图片,可以调整canny函数的参数,如本例中采用的参数是:Canny(src,dst,220,240,3)。

色彩过滤:由于身份证有很多蓝色和红色的底纹,将rgb过大的色彩变成了白色。有时候并不一定会有好的效果,反而会让边缘增多,反而影响结果。另外如果图像特别模糊,最好也不要进行色彩过滤。

最后还是需要提醒一下opencv的环境问题。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分