///////////////////////////////////////////////////////////////////////////////
//轮廓提取主函数
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <iostream>
using namespace std;
using namespace cv;
int getPixelSum(Mat& image){
int a = 0;
for (int row = 0; row < image.rows; row++) {
uchar* current_pixel = image.ptr<uchar>(row);
for (int col = 0; col < image.cols; col++) {
a += *current_pixel++; //指针遍历像素点反转颜色
}
}
return a;
}
//模板匹配函数,两图像做差
int imgMatch(Mat& image, int& rate, int& num) {
Mat imgSub;
double min = 10e6;
num = 0;
rate = 0;
for (int i = 1; i < 6; i++) {
Mat templatimg = imread("/home/lxy/图片/摄像头/" + std::to_string(i) + ".png", IMREAD_GRAYSCALE);
resize(image, image, Size(32, 48), 0, 0, cv::INTER_LINEAR); //将两图像大小调至相同
//cout << "nihao" <<endl;
resize(templatimg, templatimg, Size(32,48), 0, 0, cv::INTER_LINEAR);
absdiff(templatimg, image, imgSub);
rate = getPixelSum(imgSub);
if (rate < min) {
min = rate;
num = i;
}
rate = min;
}
return num;
}
int main()
{
//读取一张图像,转换为灰度图并进行二值化处理
Mat srcImage ; //读取图片
VideoCapture capture(2);
while (capture.isOpened()) {
capture.read(srcImage); //逐帧读取视频
if (srcImage.empty()) { //如果视频结束或未检测到摄像头则跳出循环
break;
}
//Mat srcImage ; //读取图片
Mat dstImage, grayImage, binImage;
srcImage.copyTo(dstImage); //将读取到的图片,深拷贝为dstImage
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); //转换灰度图
threshold(grayImage, binImage, 100, 255, cv::THRESH_BINARY_INV); //转换二值图,设置阈值,高于100认为255
//寻找轮廓
vector<vector<Point>> contours; //定义轮廓和层次结构
vector<Vec4i> hierarchy;
findContours(binImage, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); //寻找轮廓
int i = 0;
vector<vector<Point>>::iterator It;
Rect a4rect[15]; //假设最多不会超过15个轮廓
for (It = contours.begin(); It < contours.end(); It++) { //画出包围数字的最小矩形
a4rect[i].x = (float)boundingRect(*It).tl().x;
a4rect[i].y = (float)boundingRect(*It).tl().y;
a4rect[i].width = (float)boundingRect(*It).br().x - (float)boundingRect(*It).tl().x;
a4rect[i].height = (float)boundingRect(*It).br().y - (float)boundingRect(*It).tl().y;
if ((a4rect[i].height > 80) && (a4rect[i].width > 50) && (a4rect[i].height < 640) && (a4rect[i].width < 480)) {
rectangle(dstImage, a4rect[i], Scalar(0, 0, 255), 2, 8, 0); //在原图像中用红框画出识别到的各轮廓
rectangle(binImage, a4rect[i], Scalar(0, 0, 0), 0, 8, 0);
i++;
}
}
imshow("dstImage", dstImage);
waitKey(1);
//将图像轮廓逐一与模板匹配
Mat num[15];
int matchingNum = 0; //匹配到的数字
int matchingRate = 0; //相似率
Mat a4Img = dstImage; //提取A4纸区域
Mat a4binImg;
cvtColor(a4Img, a4binImg, CV_BGR2GRAY);
for (int j = 0; j < i; j++) {
a4binImg(a4rect[j]).copyTo(num[j]); //提取包围数字的矩形区域至num[j]
imgMatch(num[j], matchingRate, matchingNum); //数字匹配
if (matchingRate < 400000) {
cout << "识别数字:" << matchingNum << "\t匹配率:" << matchingRate << endl;
//imwrite(to_string(matchingNum) + ".jpg", num[j]);
}
}
}
system("pause");
return 0;
}
核心流程
摄像头捕获 → 2. 灰度化+二值化 → 3. 轮廓检测 → 4. 区域筛选 → 5. 模板匹配 → 6. 输出结果
示例输出
识别数字:3 匹配率:120000
识别数字:5 匹配率:95000
- 程序结构概述
cpp
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <iostream>
using namespace std;
using namespace cv;
头文件:引入 OpenCV 核心库和图像处理模块。
命名空间:使用 std 和 cv 简化代码。
2. 核心函数解析
**(1) getPixelSum(Mat& image)**
cpp
int getPixelSum(Mat& image) {
int a = 0;
for (int row = 0; row < image.rows; row++) {
uchar* current_pixel = image.ptr<uchar>(row);
for (int col = 0; col < image.cols; col++) {
a += *current_pixel++; // 累加像素值
}
}
return a;
}
功能:计算图像所有像素值的总和。
用途:在模板匹配中衡量两图像的差异(差异越小,匹配度越高)。
**(2) imgMatch(Mat& image, int& rate, int& num)**
cpp
int imgMatch(Mat& image, int& rate, int& num) {
Mat imgSub;
double min = 10e6; // 初始化最小差异为一个极大值
num = 0;
rate = 0;
for (int i = 1; i < 6; i++) {
// 读取模板图像(1.png ~ 5.png)
Mat templatimg = imread("/home/lxy/图片/摄像头/" + std::to_string(i) + ".png", IMREAD_GRAYSCALE);
// 统一图像尺寸为 32x48
resize(image, image, Size(32, 48), 0, 0, cv::INTER_LINEAR);
resize(templatimg, templatimg, Size(32, 48), 0, 0, cv::INTER_LINEAR);
// 计算两图像的绝对差异
absdiff(templatimg, image, imgSub);
rate = getPixelSum(imgSub); // 差异总和
// 更新最小差异和对应的模板编号
if (rate < min) {
min = rate;
num = i;
}
rate = min;
}
ret
urn num;
}
功能:将输入图像与 5 个模板图像(1.png ~ 5.png)逐一比较,返回最匹配的数字(1~5)。
关键步骤:
读取模板:从指定路径加载模板图像。
尺寸归一化:统一调整为 32x48 像素。
差异计算:通过 absdiff 计算像素差异,累加差异值。
最佳匹配:选择差异最小的模板编号作为识别结果。
3. 主函数 main() 解析
**(1) 初始化摄像头**
cpp
VideoCapture capture(2);
while (capture.isOpened()) {
capture.read(srcImage); // 逐帧读取视频
if (srcImage.empty()) break; // 检查是否为空帧
}
功能:打开摄像头(设备号 2),实时捕获视频帧。
**(2) 图像预处理**
cpp
Mat dstImage, grayImage, binImage;
srcImage.copyTo(dstImage); // 深拷贝原始图像
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); // 转为灰度图
threshold(grayImage, binImage, 100, 255, cv::THRESH_BINARY_INV); // 二值化(反色)
步骤:
灰度化:减少计算量。
二值化:通过阈值 100 将图像转为黑白(反色处理,便于轮廓检测)。
**(3) 轮廓检测与筛选**
cpp
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binImage, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
Rect a4rect[15]; // 存储轮廓的矩形区域
for (It = contours.begin(); It < contours.end(); It++) {
a4rect[i] = boundingRect(*It); // 计算轮廓的外接矩形
// 筛选符合条件的矩形(高度>80,宽度>50,且不超过图像尺寸)
if ((a4rect[i].height > 80) && (a4rect[i].width > 50) &&
(a4rect[i].height < 640) && (a4rect[i].width < 480)) {
rectangle(dstImage, a4rect[i], Scalar(0, 0, 255), 2, 8, 0); // 在原图画红框
i++;
}
}
功能:检测图像中的轮廓,筛选出可能是数字的区域。
关键参数:
RETR_EXTERNAL:只检测最外层轮廓。
CHAIN_APPROX_NONE:存储轮廓所有点。
筛选逻辑:排除过小或过大的矩形(避免噪声干扰)。
**(4) 数字识别与输出**
cpp
Mat num[15];
for (int j = 0; j < i; j++) {
a4binImg(a4rect[j]).copyTo(num[j]); // 提取数字区域
imgMatch(num[j], matchingRate, matchingNum); // 模板匹配
if (matchingRate < 400000) { // 差异阈值
cout << "识别数字:" << matchingNum << "\t匹配率:" << matchingRate << endl;
}
}
功能:对每个候选区域调用 imgMatch 进行模板匹配。
输出结果:打印识别到的数字和匹配差异值(差异越小越匹配)。