身份證號碼圖像提取--基于canny邊緣檢測的連通域檢測算法
來源:程序員人生 發布時間:2016-06-13 11:46:19 閱讀次數:5239次
在之前掃描2維碼提取任務以后,工作中又需要將身份證圖象中的身份證號碼提取出來,然后給同事調用進行辨認。之前的連通域檢測算法比較“蠻力”,由于它1旦檢測出1個大的區域,那末這區域中的所有內部區域都將不復存在了。所以在連通域檢測時,需要第1步去掉周圍可能存在的白邊,否則就會失敗。后來筆者換了1個思路,如果檢測1個區域時保存對應生成該區域的點,該區域不符合要求的話就將這些點擦掉,從而就不會影響到內部的區域了。因而就有了1下算法的誕生:
(1)從左上角開始,從碰到的第1個白點開始探測最大的連通域,獲得離該點小于max_dis的所有點,放到1個list中。
(2)然后遍歷該列表,并將離每個點距離小于max_dis的點都放到該list中。
(3)遍歷結束后,計算包括list中所有點的最小rect區域。
(4)根據設定的目標區域特點,如長寬、長寬比等,來判斷該區域是不是滿足要求,如果滿足,則放到rectlist中。然后將該list中的所有點都置黑。轉到(1)履行。
(5)如果rectlist為空,則沒有獲得到目標rect。如果>=1 則將之依照1個規則進行排序(應當是那個最主要的特點),然后輸出最可能的那個rect。
算法進程演示以下:
原圖:

色采過濾(為了得到效果好1點的canny圖):

canny圖:

檢測畫框與擦除:
第1次 畫框:

第1次擦除:

第2次畫框:

第2次擦除

第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中的點都設置成某1個色彩
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);
//將1個box框畫成某1個色彩
void DrowBoxColor(Mat &srcImg, std::vector<RECT> &boxList, int nColor);
//獲得1個聯通區域
BOOL GetOneConnectedDomain(Mat & srcImg, std::vector<cv::Point>& pointList, RECT &rect);
//將圖象的某1個區域保存為圖象
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 ⑴;
}
}
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;
//探測1個矩形連通域,判斷是不是符合目標特點,不符合刪除找下1個。
//找到1個放入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;
}
}
//置黑然后找下1個
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;
//找到第1個最上角的白點
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();
//探測下1個點。
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);
}
}
}
}
}
//畫框線為1個色彩
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() == ⑴)
{
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過大的色采變成了白色。有時候其實不1定會有好的效果,反而會讓邊沿增多,反而影響結果。另外如果圖象特別模糊,最好也不要進行色采過濾。
最后還是需要提示1下opencv的環境問題。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈