身份证号码简易识别(opencv + Qt)

记录了一下项目制作过程中 opencv基本API的使用,以及一些基本概念源码可至GitHub查看

身份证信息识别

1. 图片的读取 显示

imread()读取图像,imread包含两个参数:imread(图像路径, 图像形式);

namedWindow() 用来新建一个显示窗口,用来显示图像,包含两个参数:namedWindow(窗口名称, 窗口形式)

**imshow()**用于显示图像,包含两个参数:imshow(窗口名称,图像名称)

图像形式有三种

  • 加载彩色图片 (默认加载形式)

1
2
3
imread(图像路径, IMREAD_COLOR);
//或者
imread(图像路径, 1);
  • 加载灰度模式图像

1
2
3
imread(图像路径, IMREAD_GRAYSCALE);
//或者
imread(图像路径, 0);
  • 加载图像,包括alpha通道

1
2
3
imread(图像路径,IMREAD_UNCHANGED);
//或者
imread(图像路径, -1);

窗口形式

  • 显示的图像大小不能改变(默认形式)

    namedWindow(窗口名称, WINDOW_AUTOSIZE) 1

  • 图像大小能够调节

    namedWindow(窗口名称, WINDOW_NORMAL) 0

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main()
{
Mat image; //创建一个空图像image
image = imread("D://work//c++//Imageprocessing1//企鹅.jpg"); //读取文件夹中的图像

//检测图像是否加载成功
if (image.empty()) //检测image有无数据,无数据 image.empty()返回 真
{
cout << "Could not open or find the image" << endl;
return -1;
}

namedWindow("IMAGE"); //创建显示窗口,不加这行代码,也能显示,默认窗口大小不能改变
imshow("IMAGE", image); //在窗口显示图像

imwrite("1.png", image); //保存图像为png格式,文件名称为1

waitKey(0); //暂停,保持图像显示,等待按键结束

return 0;

}

2. qt中的string转化为opencv中的string

先调用 toLocal8Bit() 方法将 fileName 转换为本地8位编码的 QByteArray,然后再调用 data() 方法获取该 QByteArray 中存储的 C 风格字符串的指针

1
2
3
4
5
6
void ID_card_recognition::read_image(const QString& fileName)
{
QString tmpPath = fileName.toLocal8Bit().data();//将 fileName 转换为本地8位编码的 C 风格字符串
Mat image; //创建一个空图像image
image = imread(tmpPath.toStdString(), IMREAD_COLOR); //读取文件夹中的图像
}

3. 二值化处理

二值化处理(Binarization)是一种图像处理技术,它将图像中的像素值根据特定的阈值转化为两种值(通常是0和255),从而简化图像的数据表示。

通常需要先将图片转换为灰度图像再进行二值化处理

  • void threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);

threshold() 函数是 OpenCV 中用于图像二值化的函数,它将输入图像的像素值根据指定的阈值进行分割,生成一个二值化图像

  1. cv::THRESH_BINARY:如果源图像像素值大于阈值,则输出图像的对应像素值设置为maxval,否则设置为0。

  2. cv::THRESH_BINARY_INV:与THRESH_BINARY相反,如果源图像像素值大于阈值,则输出图像的对应像素值设置为0,否则设置为maxval

  3. cv::THRESH_TRUNC:如果源图像像素值大于阈值,则输出图像的对应像素值设置为阈值thresh,否则保持源图像的像素值不变。

  4. cv::THRESH_TOZERO:如果源图像像素值大于阈值,则输出图像的对应像素值保持不变,否则设置为0。

  5. cv::THRESH_TOZERO_INV:与THRESH_TOZERO相反,如果源图像像素值大于阈值,则输出图像的对应像素值设置为0,否则保持源图像的像素值不变。

  6. cv::THRESH_MASK:这是一个掩码值,用于与上述其他值进行按位或操作,以保持旧的OpenCV兼容性。

  7. cv::THRESH_OTSU:在使用THRESH_BINARYTHRESH_BINARY_INV阈值类型时,这个标志可以自动选择最优的阈值。它会计算源图像的直方图,并使用大津二值化方法来找到将图像分为两个类别的最佳阈值。

  8. cv::THRESH_TRIANGLE:这是另一个自动选择阈值的标志,它使用三角形方法来找到最优阈值,这种方法通常比大津方法更快,但在某些情况下可能不如大津方法准确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
// 读取灰度图像
Mat image = imread("image.jpg", IMREAD_GRAYSCALE);
if (image.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}

// 设置阈值和最大值
int thresh_value = 0;
int max_value = 255;

// 应用二值化
Mat binary_image;
threshold(image, binary_image, thresh_val, max_value, THRESH_BINARY | THRESH_OTSU);
/*THRESH_BINARY:大于阈值的像素赋值为 maxval,小于等于阈值的像素赋值为 0。
THRESH_OTSU可以实现 Otsu's 二值化方法,即自动选择最优的阈值,而无需手动指定阈值
二者通常结合使用 使用Otsu's时thresh 参数可以设置为 0,因为该值不会被实际使用*/

// 显示结果
imshow("Binary Image", binary_image);
waitKey(0);
destroyAllWindows();

return 0;
}

4. 双边滤波

双边滤波(Bilateral Filter)是一种非线性滤波技术,它能够在保持边缘清晰的同时有效地去除图像中的噪声

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT);

  • d:滤波器的直径。如果为 -1,则根据 sigmaSpace 自动计算。

  • sigmaColor:颜色空间的标准差,较大的值表示颜色空间中更大的像素距离将相互混合。

  • sigmaSpace:空间域的标准差,较大的值表示在空间域内的像素距离将相互混合。

  • borderType:用于处理边界的类型,默认为 BORDER_DEFAULT

5. 查找轮廓

轮廓可以看作是将具有相同颜色或灰度值的所有连续点(沿着边界)连接起来的曲线

1
2
3
4
5
6
7
8
9
10
//函数原型
void cv::findContours(
InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
);
//在黑色背景中寻找白色轮廓
  1. image:输入图像(8位单通道图像),也是输出图像。

    • 图像必须是二值化图像(通常通过 cv::thresholdcv::Canny 等方法预处理)。
    • 该函数会修改输入图像,建议在调用该函数之前对图像进行备份。
  2. contours:输出参数,用于存储检测到的轮廓。

    • 轮廓存储为点的向量的向量,每个轮廓是一个 std::vector<cv::Point> 类型的对象。
  3. hierarchy:输出参数,用于存储每个轮廓的层次结构信息。

    • 这是一个 std::vector<cv::Vec4i> 类型的向量。
    • hierarchy[i][0] 表示后一个轮廓的索引。
    • hierarchy[i][1] 表示前一个轮廓的索引。
    • hierarchy[i][2] 表示父轮廓的索引。
    • hierarchy[i][3] 表示第一个子轮廓的索引。
  4. mode:轮廓检索模式,决定轮廓的提取方式和层次结构。

    • cv::RETR_EXTERNAL:只检索最外层的轮廓。
    • cv::RETR_LIST:检索所有轮廓,但不建立层次结构。
    • cv::RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。
    • cv::RETR_TREE:检索所有轮廓并重建嵌套轮廓的完整层次结构。
  5. method:轮廓近似方法,决定如何存储轮廓点。

    • cv::CHAIN_APPROX_NONE:存储所有轮廓点。
    • cv::CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,只保留它们的端点。
    • cv::CHAIN_APPROX_TC89_L1cv::CHAIN_APPROX_TC89_KCOS:使用 Teh-Chin 链近似算法。
  6. offset(可选):可选参数,用于对输出的轮廓点坐标进行偏移,默认为 (0, 0)

6. 绘制轮廓

1
2
3
4
5
6
7
8
9
10
11
12
//函数原型
void cv::drawContours(
InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point()
);
  1. image:输入输出参数,用于绘制轮廓的图像。

    • 类型为 cv::InputOutputArray,通常为 cv::Mat 类型。
    • 这是一个修改后的图像,轮廓会被绘制在该图像上。
  2. contours:输入参数,表示轮廓的集合。

    • 类型为 cv::InputArrayOfArrays,通常是 std::vector<std::vector<cv::Point>> 类型。
    • 每个轮廓都是一个点的集合,表示图像中的一个连通区域。
  3. contourIdx:输入参数,指定绘制哪个轮廓的索引。

    • 如果为负值(例如 -1),则绘制所有轮廓。
  4. color:输入参数,指定绘制轮廓的颜色。

    • 类型为 cv::Scalar,例如 cv::Scalar(0, 255, 0) 表示绿色。
  5. thickness(可选):输入参数,指定绘制轮廓的线条粗细。

    • 默认值为 1
    • 如果值为 FILLED 或负值,则填充整个轮廓。
  6. lineType(可选):输入参数,指定线条的类型。

    • 默认值为 cv::LINE_8,表示 8-连接线。
    • 可以为 cv::LINE_4(4-连接线)或 cv::LINE_AA(抗锯齿线)。
  7. hierarchy(可选):输入参数,指定轮廓的层次结构。

    • 类型为 cv::InputArray,通常是 std::vector<cv::Vec4i> 类型。
    • 如果不需要层次结构,可以传递 cv::noArray()
  8. maxLevel(可选):输入参数,指定绘制轮廓的最大层次。

    • 默认值为 INT_MAX,表示绘制所有层次的轮廓。
    • 例如,如果设置为 0,则只绘制最外层的轮廓。
  9. offset(可选):输入参数,指定绘制轮廓时的偏移量。

    • 类型为 cv::Point,默认值为 cv::Point(0, 0)

7 绘制轮廓(二)

7.1 cv::boundingRect 函数

用于计算给定点集或轮廓的最小边界矩形。这个矩形是完全包含输入轮廓的最小矩形,并且其边与坐标轴对齐。

1
cv::Rect cv::boundingRect(InputArray points);
  • points:输入的点集,可以是一个轮廓(如 std::vector<cv::Point>)或者是 cv::Mat 类型的点集。

返回值

  • 返回一个 cv::Rect 对象,表示包含所有输入点的最小边界矩形。

7.2 cv::rectangle 函数

用于在图像上绘制矩形。可以根据需要设置矩形的位置、大小、颜色和线条厚度等属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//函数原型
void cv::rectangle(
InputOutputArray img,
Point pt1,
Point pt2,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
);
或者
void cv::rectangle(
InputOutputArray img,
const Rect& rec,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
);

  • img:输入输出图像,用于绘制矩形的图像。

  • pt1:矩形的一个顶点。

  • pt2:矩形的对角顶点。

  • rec:表示矩形的 cv::Rect 对象。

  • color:矩形的颜色,使用 cv::Scalar 定义,例如 cv::Scalar(0, 255, 0) 表示绿色。

  • thickness:线条厚度。默认值为 1。如果值为负数(如 FILLED),则填充矩形。

  • lineType:线条类型,可以是 cv::LINE_8cv::LINE_4cv::LINE_AA

  • shift:坐标点的小数点位数,默认值为 0

示例代码

1
2
3
4
5
6
7
8
9
10
11
for (size_t i = 0; i < contours.size(); i++) {
// 计算轮廓的最小边界矩形
cv::Rect boundingRect = cv::boundingRect(contours[i]);
// 在图像上绘制边界矩形
cv::rectangle(image, boundingRect, cv::Scalar(0, 255, 0), 2);

// 计算并标记矩形的中心点
cv::Point center = (boundingRect.tl() + boundingRect.br()) * 0.5;
cv::circle(image, center, 5, cv::Scalar(255, 0, 0), -1);
}

8. 腐蚀 与 膨胀

image-20240526125045766 image-20240526125148573

9. 开操作 闭操作

开操作先腐蚀后膨胀的操作称之为开操作。主要用于消除小的物体、在纤细点处分离物体、平滑较大物体的边界,同时并不明显改变其面积。此外,开操作还可以用于提取水平或竖直的线条

闭操作先膨胀后腐蚀 主要用于填充物体内的小空洞、连接邻近物体、平滑其边界,同时并不明显改变其面积 可以用于填充小的封闭区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("path_to_your_image.jpg", cv::IMREAD_COLOR);
if (image.empty()) {
std::cerr << "无法读取图像" << std::endl;
return -1;
}

// 显示原始图像
cv::imshow("原始图像", image);

// 定义结构元素,用于开操作和闭操作
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
/*kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10)) # 椭圆结构
kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (10, 10)) # 十字结构*/


// 执行开操作
cv::Mat open_result;
cv::morphologyEx(image, open_result, cv::MORPH_OPEN, kernel);

// 显示开操作结果
cv::imshow("开操作结果", open_result);

// 执行闭操作
cv::Mat close_result;
cv::morphologyEx(image, close_result, cv::MORPH_CLOSE, kernel);

// 显示闭操作结果
cv::imshow("闭操作结果", close_result);

// 等待按键后退出
cv::waitKey(0);

return 0;
}

10. 模板匹配

void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method);

  • image:输入图像,必须是 8 位或 32 位浮点数。

  • template:模板图像,必须是相同类型和深度的大小小于或等于输入图像的图像。

  • result:匹配结果图像,是一个单通道 32 位浮点数。每个像素表示该区域与模板的匹配程度。

  • method指定的匹配方法,可以是以下之一:

    • TM_SQDIFF:平方差匹配法,计算模板与图像之间的平方差,值越小表示匹配度越高。
    • TM_SQDIFF_NORMED:归一化平方差匹配法,计算归一化的平方差,值越小表示匹配度越高。
    • TM_CCORR:相关性匹配法,计算模板与图像之间的相关性,值越大表示匹配度越高。
    • TM_CCORR_NORMED:归一化相关性匹配法,计算归一化的相关性,值越大表示匹配度越高。
    • TM_CCOEFF:相关系数匹配法,计算模板与图像之间的相关系数,值越大表示匹配度越高。
    • TM_CCOEFF_NORMED:归一化相关系数匹配法,计算归一化的相关系数,值越大表示匹配度越高。

void minMaxLoc(InputArray src, double minVal, double maxVal = 0, Point* minLoc = 0, Point* maxLoc = 0, InputArray mask = noArray());**

用于在给定的矩阵中找到最小值和最大值,并返回它们的位置。这个函数在图像处理中非常有用,尤其是在需要找到图像中的最亮或最暗点、或者在模板匹配中找到最佳匹配位置时

  • src:输入的单通道数组。

  • minVal:返回最小值的指针(如果不需要,则可以设置为 NULL)。

  • maxVal:返回最大值的指针(如果不需要,则可以设置为 NULL)。

  • minLoc:返回最小值位置的指针(如果不需要,则可以设置为 NULL)。

  • maxLoc:返回最大值位置的指针(如果不需要,则可以设置为 NULL)。

  • mask:用于选择数组元素的感兴趣区域,如果不需要,则使用 noArray()

void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR);

缩放图片

  • src:输入图像,可以是任意通道数的 Mat 对象。

  • dst:输出图像,大小为 dsize,或者与 src 相同类型和大小的 Mat 对象。

  • dsize:输出图像的大小。如果这个参数为 0,则它由 fxfy 参数决定。

  • fx:水平方向的缩放比例。如果这个参数为 0,则它由 dsize 参数决定。

  • fy:垂直方向的缩放比例。如果这个参数为 0,则它由 dsize 参数决定。

  • interpolation插值方法,用于在缩放时计算新像素值。可选的插值方法包括:

    • INTER_NEAREST:最近邻插值。
    • INTER_LINEAR:双线性插值(默认值)。
    • INTER_AREA:使用像素区域关系重采样。当图像缩小时,这种方法可以避免波纹出现,通常用于图像缩放。
    • INTER_CUBIC:三次样条插值。
    • INTER_LANCZOS4:Lanczos 插值,超过 8x8 像素邻域的采样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if (ID_number.size() != 18) return false;
for (int i = 0; i < 18; i++)
{
Mat roi = ID_number[i].mat;
resize(roi, roi, Size(30, 40), 0, 0, INTER_AREA);
Mat gray_image;
cvtColor(roi, gray_image, cv::COLOR_BGR2GRAY);
Mat binary_image;
threshold (gray_image, binary_image, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

int maxIndex = 0;
double Max = 0.0;

for (int j = 0; j < 11; j++)
{
Mat template_num = templates_cards[j].mat;
resize(template_num, template_num, Size(30, 40), 0, 0, INTER_AREA);
Mat gray_template_num;
cvtColor(template_num, gray_template_num, cv::COLOR_BGR2GRAY);
Mat binary_template_num;
threshold(gray_template_num, binary_template_num, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

Mat result;
matchTemplate(binary_image, binary_template_num, result, TM_CCOEFF_NORMED);
double minVal, maxVal;
Point minLoc, maxLoc;

minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

if (maxVal > Max)
{
Max = maxVal;
maxIndex = j;
}
}
result[i] = maxIndex;
}