-
图像格式转换
使用
UIImage
到cv::Mat
的转换时,注意通道顺序(iOS使用BGRA格式)。 -
性能优化
- 对于移动端,可降低HOG参数复杂度(如减少方向数)。
- 使用
@autoreleasepool
管理内存。
-
动态阈值
建议根据实际数据集通过ROC曲线确定最佳阈值。
-
错误处理
增加对空图像、无轮廓等异常情况的检查。
** 关键步骤:**
1.引入OpenCV库
2.涉及C++,需要将.m文件更新为.mm文件
#pragma mark - 图像预处理
// 将UIImage转换为cv::Mat
+ (cv::Mat)preprocessSignatureImage:(UIImage *)image {
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4); // 8-bit 4通道
CGContextRef contextRef = CGBitmapContextCreate(
cvMat.data, cols, rows, 8, cvMat.step[0], colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault
);
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
// 转为灰度图
Mat gray;
cvtColor(cvMat, gray, COLOR_BGRA2GRAY);
// 二值化(Otsu)
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
// 形态学去噪(开运算)
Mat kernel = getStructuringElement(MORPH_RECT, cv::Size(3, 3));
morphologyEx(binary, binary, MORPH_OPEN, kernel);
// 尺寸归一化
resize(binary, binary, cv::Size(128, 64));
return binary;
}
mark - 特征提取
+ (NSArray<NSNumber *> *)extractFeaturesFromImage:(cv::Mat)binaryImage {
vector<float> features;
// --------------------- HOG特征 ---------------------
HOGDescriptor hog(
cv::Size(128, 64), // 窗口尺寸
cv::Size(16, 16), // 块尺寸
cv::Size(8, 8), // 块步长
cv::Size(8, 8), // 胞元尺寸
9 // 方向数
);
vector<float> hogFeatures;
hog.compute(binaryImage, hogFeatures);
features.insert(features.end(), hogFeatures.begin(), hogFeatures.end());
// --------------------- 轮廓特征 ---------------------
vector<vector<cv::Point>> contours;
findContours(binaryImage.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 添加轮廓数量
features.push_back(contours.size());
// 添加面积占比
if (!contours.empty()) {
double area = contourArea(contours[0]);
features.push_back(area / (binaryImage.cols * binaryImage.rows));
} else {
features.push_back(0.0);
}
// 转为NSArray
NSMutableArray *result = [NSMutableArray array];
for (float val : features) {
[result addObject:@(val)];
}
return result;
}`
#pragma mark - 相似度计算
+ (double)cosineSimilarityBetweenFeature1:(NSArray<NSNumber *> *)feat1
feature2:(NSArray<NSNumber *> *)feat2 {
if (feat1.count != feat2.count) {
NSLog(@"特征维度不匹配");
return -1;
}
double dotProduct = 0.0, norm1 = 0.0, norm2 = 0.0;
for (NSUInteger i = 0; i < feat1.count; i++) {
double val1 = feat1[i].doubleValue;
double val2 = feat2[i].doubleValue;
dotProduct += val1 * val2;
norm1 += val1 * val1;
norm2 += val2 * val2;
}
return dotProduct / (sqrt(norm1) * sqrt(norm2));
}
//比对方法
+ (BOOL)compareSignaturesWithImage1:(UIImage *)image1 image2:(UIImage *)image2 similarityThreshold:(double)threshold{
// 获取签名图像
UIImage *signature1 = image1;
UIImage *signature2 = image2;
// 预处理
cv::Mat binary1 = [ImageProcessor preprocessSignatureImage:signature1];
cv::Mat binary2 = [ImageProcessor preprocessSignatureImage:signature2];
// 特征提取
NSArray *feat1 = [ImageProcessor extractFeaturesFromImage:binary1];
NSArray *feat2 = [ImageProcessor extractFeaturesFromImage:binary2];
// 计算相似度
double similarity = [ImageProcessor cosineSimilarityBetweenFeature1:feat1
feature2:feat2];
NSLog(@"签名相似度:%lf",similarity);
// 判断结果(阈值根据实际数据调整)
if (similarity > threshold) {
return YES;
} else {
return NO;
}
}
使用方法:
很简单,isSamePic是阈值,这个根据实际情况你可以设置动态的,这样就可以计算出来两张照片的相似度
double threshold = 0.7;
BOOL isSamePic = [ImageProcessor compareSignaturesWithImage1:self.s1.image image2:self.s2.image similarityThreshold:threshold];
if (isSamePic) {
NSLog(@"两张签名照片可能是同一个人的签名。");
} else {
NSLog(@"两张签名照片可能不是同一个人的签名。");
}