0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

C语言专家集中营 来源:lq 2019-01-22 13:54 次阅读

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

(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的环境问题。

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

    关注

    2

    文章

    1083

    浏览量

    40396
  • 代码
    +关注

    关注

    30

    文章

    4737

    浏览量

    68305
  • 检测算法
    +关注

    关注

    0

    文章

    119

    浏览量

    25208

原文标题:身份证号码图像提取--基于canny边缘检测的连通域检测算法

文章出处:【微信号:C_Expert,微信公众号:C语言专家集中营】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    基于Canny边缘检测算子的图像检索算法

    【摘要】:针对依赖传统Canny算子的基于边缘的图像检索系统所存在的不足,提出一种基于Canny边缘检测的图像检索
    发表于 04-24 10:03

    用DM642开发板做的canny边缘检测算法(附CCS源码

    本帖最后由 mr.pengyongche 于 2013-4-30 02:24 编辑 用DM642开发板做的canny边缘检测算法(附CCS源码),如果需要其他的相关资料大家可以向我要,我会将更多图像处理的资料上传
    发表于 03-31 14:50

    关于canny算子边缘检测的问题

    本帖最后由 豆吖豆 于 2017-4-4 23:14 编辑 grd=edge(Egray,'canny',0.09,'both');大神门 问一下这个后面的0.09和both什么意思是指的是Egray图像的上下大小还是,另外可以的话能大概说说这个canny
    发表于 04-04 22:27

    图像边缘检测算法体验步骤(Photoshop,Matlab)

    图像边缘检测算法体验步骤(Photoshop,Matlab)1. 确定你的电脑上已经安装了Photoshop和Matlab2. 使用手机或其他任何方式,获得一张彩色图像(任何格式),建议图像颜色丰富
    发表于 03-06 10:51

    基于Qualcomm FastCv的边缘检测算法详解

    微分边缘检测算法主要是基于图像强度的一阶和二阶导数,而导数的计算对噪声很敏感,噪声的存在可能会使检测到的边缘变宽或在某些点处发生间断,因此,需要使用滤波器来滤掉噪声。大多数滤波器在降低
    发表于 09-21 11:45

    图象处理中的哈夫变换和Canny边缘检测算法

    图象处理中的边缘检测------canny算子
    发表于 03-16 06:48

    基于Canny边缘检测算子的图像检索算法

      针对依赖传统Canny算子的基于边缘的图像检索系统所存在的不足,提出一种基于Canny边缘检测的图像检索
    发表于 02-11 11:22 28次下载

    基于Canny算法的改进Kirsch人脸边缘检测方法

    针对Kirsch边缘检测算法的不足,提出了一种基于Canny算法改进的Kirsch人脸边缘检测算法
    发表于 02-23 14:31 10次下载

    医学图像边缘检测算法的研究

    边缘检测是医学图像处理中非常重要的一个环节,通过对几种经典边缘检测算法的分析,提出了一种基于Canny算子的改进
    发表于 07-05 16:50 15次下载

    基于Canny检测算法实现的目标跟踪

    为了设计一种实时高效、稳定可靠的图像目标跟踪系统平台,避免因图像边缘提取效果差而引起跟踪失败,采用自适应Canny边缘检测算法。该自适应算法
    发表于 03-05 16:14 38次下载
    基于<b class='flag-5'>Canny</b><b class='flag-5'>检测算法</b>实现的目标跟踪

    canny边缘检测

    《OpenCV3编程入门》书本配套源代码canny边缘检测
    发表于 06-06 15:20 2次下载

    基于改进Canny的图像边缘检测算法

    图像边缘是计算机理解图像的重要特征之一。在数字图像中,边缘就是相邻的具有显著不同特征区域间的分界线。在机器视觉领域,对边缘检测算法进行了深入的研究,得到了各种针对不同领域图像的
    发表于 11-02 15:15 19次下载
    基于改进<b class='flag-5'>Canny</b>的图像<b class='flag-5'>边缘</b><b class='flag-5'>检测算法</b>

    Robinson边缘检测算法

    传统的Canny边缘检测算子是一种含有最优化思想的算子,它具有较高的检测精度,可以达到单像素级,但是因为它本身对噪声比较敏感,所以需要先利用Gauss滤波、均值滤波、中值滤波等滤波器进
    发表于 12-01 14:13 0次下载

    canny算子的语法原理分析

    Canny算子Canny边缘检测算子是JohnF.Canny于1986年开发出来的一个多级边缘
    发表于 12-18 17:55 8850次阅读
    <b class='flag-5'>canny</b>算子的语法原理分析

    关于边缘检测算子的实现原理

    Canny 边缘检测算法 是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法
    的头像 发表于 01-05 11:41 1392次阅读