基于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
#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 5 && nheight 100 && nwidth 8 && frate 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 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 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 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 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 nright)
{
nright = temppoint.x;
}
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 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 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< return 0;
}
说明:
由于不断的进行循环检测,如果像素过高图片太大则耗时较多,而且边缘检测效果特别不好,所以程序中对于像素宽度大于900的则缩放到400。
程序运行效果的好坏直接影响因数是 canny图片的效果。所以对于不同特点的图片,可以调整canny函数的参数,如本例中采用的参数是:canny(src,dst,220,240,3)。
色彩过滤:由于身份证有很多蓝色和红色的底纹,将rgb过大的色彩变成了白色。有时候并不一定会有好的效果,反而会让边缘增多,反而影响结果。另外如果图像特别模糊,最好也不要进行色彩过滤。
最后还是需要提醒一下opencv的环境问题。

关于低压断路器口诀,你都理解吗
北斗卫星导航定位芯片行业:华大北斗与中交星宇签署战略合作协议
大唐建全球最大5G多场景试验网加速向5g时代迈进
降低电网环节收费和输配电价格,确保降价成果真正惠及终端用户
RK3588与飞腾D2000参数对比
基于canny边缘检测的连通域检测算法
BUCK与BOOST电路设计与器件选型基础
小米MIX折叠屏手机高清渲染图曝光
麒麟信安发布基于openEuler 22.03 LTS 的商业发行版麒麟信安服务器操作系统V3.5.2
我国自动移液工作站近五年间复合增长率达到10%,,未来发展前景较好
国产存储控制芯片该如何实现高速发展
R5300 G4服务器无法正常进入操作系统且日志显示硬盘异常分析
Victor发布支持 I2C编程的Cool Power ZVS降压稳压器
DCS-Control拓扑在新一代电源设计中能做到哪些权衡?
关于嵌入式Linux启动
了解LabVIEW FPGA和软件设计射频仪器的优势所在[图]
碳化硅功率模块及电控的设计、测试与系统评估
欧洲晶圆代工注定失败?欧洲为什么缺乏先进制程的晶圆厂呢?
研祥终端智慧医疗产品解决方案
动力电池衰减的原因有哪些