身份证号码图像提取--基于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 <iostream>
#include < io.h>
#include <algorithm>
#include <stdio.h>
#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<cv::Point> pointList, int nColor);
	//根据点列表获取最小包含区域
	void GetRectFromPointList(std::vector<cv::Point>& pointList, RECT & rtRect);
	//获取与该点临近的点
	void GetNearPoint(Mat & srcImg,cv::Point currentPoint, std::vector<cv::Point> & pointList);
	//将一个box框画成某一个颜色
	void DrowBoxColor(Mat &srcImg, std::vector<RECT> &boxList, int nColor);
	//获取一个联通区域
	BOOL GetOneConnectedDomain(Mat & srcImg, std::vector<cv::Point>& 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;
};

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;x<workImg->height;x++)
	{
		for(int y=0;y<workImg->width;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<cv::Point> 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<cv::Point> pointList;
	//探测一个矩形连通域,判断是否符合目标特征,不符合删除找下一个。
	//找到一个放入vector中。
	std::vector<RECT> 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<cv::Point>& 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<RECT> 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<cv::Point>& 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<cv::Point> & 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<cv::Point>::iterator itFind =  find( pointList.begin(), pointList.end(),tempPint);
				if (itFind == pointList.end())
				{
					pointList.push_back(tempPint);
				}
			}
		}
	}
}
//画框线为一个颜色
void CGetIDCOde::DrowBoxColor(Mat &srcImg, std::vector<RECT> &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 "<<rect.left<<" "<<rect.top<<" "<<rect.bottom<<" "<<rect.right<<" ";
	return 0;

}

说明:

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

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

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

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

时间: 2024-09-30 11:20:25

身份证号码图像提取--基于canny边缘检测的连通域检测算法的相关文章

4. 基于深度学习的目标检测算法的综述(转)

4. 基于深度学习的目标检测算法的综述(转) 原文链接:https://www.cnblogs.com/zyly/p/9250195.html 目录 一 相关研究 1.选择性搜索(Selective Search) 2.OverFeat 二.基于区域提名的方法 1.R-CNN 2.SPP-Net 3.Fast R-CNN 4.Faster R-CNN 5.R-FCN 三 端对端的方法 1.YOLO 2.SSD 四 总结 在前面几节中,我们已经介绍了什么是目标检测,以及如何进行目标检测,还提及了滑

基于mindwave脑电波进行疲劳检测算法的设计(5)

时隔两个多月了,前段时间在弄Socket,就没有弄这个了.现在好了,花了几天的时间,终于又完成了一小部分了.这一小节主要讲α,β,δ,θ等等波段之间的关系.废话不多说,直接给出这几天的成果. 上一次,我们分析了attention(专注度)和meditation(冥想度)与疲劳之间的关系.如下图 上面的曲线上一小节已经简单说明了,现在要说明的是曲线的前半部分是普通测试,后面一小段两对线有点分离的部分是模拟闭眼休息状态,全身放轻松.从图中可以看出是否精神集中从专注度和冥想度是可以简单的看出来了. 接

Java验证身份证号码及提取生日信息

Java学习第一站,导师给布置的作业题,验证身份证号码的合法性并提取生日信息.第一次写Java代码,第一次用博客记录学习进程,略紧张,对Java变量的命名还停留在C++的命名法阶段,吼吼~~现在开始正题.问题说明:目前,我国大部分地区都使用的是第二代身份证,第二代身份证号码的位数为18位.但公安部没有下发统一的关于停用第一代身份证的文件,第一代身份证中有15位和18位身份证号的混用.15位身份证号码:排列顺序从左至右依次为:6位数字地址码,6位数字出生日期码,3位数字顺序码,其中出生日期码不包含

第二十八节、基于深度学习的目标检测算法的综述

在前面几节中,我们已经介绍了什么是目标检测,以及如何进行目标检测,还提及了滑动窗口,bounding box.以及IOU,非极大值抑制等概念. 这里将会综述一下当前目标检测的研究成果,并对几个经典的目标检测算法进行概述,本文内容来自基于深度学习的目标检测,在后面几节里,会具体讲解每一种方法. 在深度度学习的目标检测算法兴起之前,传统的目标检测算法是怎样的呢? 传统的目标检测一般使用滑动窗口的框架,主要包括三个步骤: 利用不同尺寸的滑动窗口框住图中的某一部分作为候选区域: 提取候选区域相关的视觉特

基于mindwave脑电波进行疲劳检测算法的设计(4)

上一次的实验做到可以从pc端读取到MindWave传输过来的脑电波原始数据了. 我是先定义一个结构体,该结构体对应保存所有能从硬件中取到的原始数据. 1 struct FD_DATA 2 { 3 int battery;//电量 4 int poor_signal;//连接质量 5 int attention;//专注度 6 int meditation;//冥想度 7 int raw;//原始数据 8 int delta;//δ波段 9 int theta;//θ脑波 10 int alpha

从入门到放弃之基于个人微博公共事件检测算法的研究与实现

毕设选择了这个题目,水平大概就是边做边学吧.当我注册了微博开放平台,下好了Java SDK搞了半天之后,才仔细一看,发现这是4年前的,或许能凑活着用吧.但这都不是重点,问题是第一步咋就GG了... ... 为什么我搞好了之后运行OAuth4Code之后,浏览器提示错误码:21322,将高级信息里的授权回调页改成官方默认的 https://api.weibo.com/oauth2/default.html 配置里边也改了,依然报这个错,而且也没有如网上所说的出来登录界面啊,这个报错是不是应该在登录

iOS身份证号码识别

一.前言 ??身份证识别,又称OCR技术.OCR技术是光学字符识别的缩写,是通过扫描等光学输入方式将各种票据.报刊.书籍.文稿及其它印刷品的文字转化为图像信息,再利用文字识别技术将图像信息转化为可以使用的计算机输入技术. ??因为项目需要,所以这些天查阅了相关资料,想在网上看看有没有大神封装的现成的demo可以用.但是无果,网上关于ocr这一块的资料很少,比较靠谱的都是要收费的,而且价格也不便宜.但是在天朝,收费感觉心里不爽,所以就决定自己研究一番. ??先上一个最终实现的效果(如果mac不是r

【HR必看】Excel中对身份证号码的处理技巧

[HR必看]Excel中对身份证号码的处理技巧 身份证号码的处理是HR部门经常遇到的问题,我给几个地方的企业培训时发现,相当一部分人员基本还是手动对身份证号码进行处理,效率可想而知啦!下面,刘老师为大家深入讲解Excel中,与身份证号码处理相关的方方面面. 一.录入技巧 1.手动输入问题与解决方法 问题:在Excel中输入身份证号码时,发现输入后变成了科学计数格式,如图: 使用单引号" ' "强制转成文本类型,发现后三位竟然全变成了0. 咋办呢? 原因:Excel中输入大于11位数字时

基于深度学习的目标检测研究进展

前言 开始本文内容之前,我们先来看一下上边左侧的这张图,从图中你看到了什么物体?他们在什么位置?这还不简单,图中有一个猫和一个人,具体的位置就是上图右侧图像两个边框(bounding-box)所在的位置.其实刚刚的这个过程就是目标检测,目标检测就是"给定一张图像或者视频帧,找出其中所有目标的位置,并给出每个目标的具体类别". 目标检测对于人来说是再简单不过的任务,但是对于计算机来说,它看到的是一些值为0~255的数组,因而很难直接得到图像中有人或者猫这种高层语义概念,也不清楚目标出现在