最大类间方差法(Otsu算法)是一种经典的图像二值化方法,广泛应用于寻找图像的最优阈值。以下是用Objective-C实现Otsu算法的详细代码解析和实现思路。
OTSU算法概述
OTSU算法通过计算图像中不同阈值对应的类间方差值,选择方差值最大的阈值作为图像的二值化阈值。其核心思想是:找出使得图像中两类区域的方差最大的阈值,通常用来进行图像的二色化处理。
算法实现步骤
1. **计算概率分布**:首先,计算图像中每个可能的灰度值的出现概率。这一步可以通过遍历图像的每个像素,统计每个灰度值的出现次数,然后转换为概率。
2. **计算累积概率分布**:接下来,计算累积概率分布,用于后续的方差计算。这一步可以通过对概率分布进行逐次累加得到。
3. **计算方差**:对于每个可能的阈值,计算图像中两类区域的方差。方差越大,说明两个区域的差异越大,通常对应的阈值越合适。
4. **选择最大方差值**:找出方差值最大的阈值作为最终的二值化阈值。这个值通常是一个灰度值,用于将图像转换为二色图像。
Objective-C实现代码
#import <Foundation/Foundation.h> #import "OtsuAlgorithm.h"
@interface OtsuAlgorithm () @property (nonatomic, strong) UIImage *sourceImage; @property (nonatomic, assign) int threshold; @property (nonatomic, assign) float *pixelValues; @property (nonatomic, strong) NSArray *probabilityDistribution; @property (nonatomic, strong) NSArray *cumulativeProbability; @end>
@implementation OtsuAlgorithm
-
(id)initWithImage:(UIImage *)image { self = [super init]; self.sourceImage = image; self.threshold = 0; self.pixelValues = [self getPixelValues]; self.probabilityDistribution = [self getProbabilityDistribution]; self.cumulativeProbability = [self getCumulativeProbability]; return self; }
-
(NSArray *)getPixelValues { CGSize imageSize = self.sourceImage.size; uint32_t *pixels = (uint32_t *)malloc(imageSize.width * imageSize.height);
for (int y = 0; y < imageSize.height; y++) { for (int x = 0; x < imageSize.width; x++) { int pixel = [self.sourceImage pixelAtX:x y:y]; *pixels + (y * imageSize.width + x) = pixel; } }
free(pixels); return [self intArrayToFloatArray:pixels]; }
-
(NSArray *)getProbabilityDistribution { int numPixels = self.pixelValues.count; double sum = 0.0;
for (float value in self.pixelValues) { sum += value; }
if (sum == 0) { return [self uniformDistribution]; }
double *prob = malloc(sizeof(double) * self.pixelValues.count);
for (int i = 0; i < self.pixelValues.count; i++) { prob[i] = self.pixelValues[i] / sum; }
return [self floatArrayToIntegerArray:prob]; }
-
(NSArray *)getCumulativeProbability { if (!self.probabilityDistribution) { return nil; }
double *cumProb = malloc(sizeof(double) * self.probabilityDistribution.count);
cumProb[0] = self.probabilityDistribution[0]; for (int i = 1; i < self.probabilityDistribution.count; i++) { cumProb[i] = cumProb[i-1] + self.probabilityDistribution[i]; }
return [self doubleArrayToIntegerArray:cumProb]; }
-
(float *)getThresholdValue { if (!self.cumulativeProbability) { return nil; }
int maxThreshold = self.pixelValues.max - 1; int minThreshold = 0;
for (int t = minThreshold; t <= maxThreshold; t++) { double sumOfSquare = 0.0; double sumOfProduct = 0.0;
for (int i = 0; i < self.cumulativeProbability.count; i++) { sumOfSquare += (self.cumulativeProbability[i] - t) * (self.cumulativeProbability[i] - t); sumOfProduct += self.cumulativeProbability[i] * (self.cumulativeProbability[i] - t); } double variance = sumOfSquare / self.cumulativeProbability[0] - sumOfProduct / self.cumulativeProbability[0]; if (variance > self.threshold) { self.threshold = t; }
}
return (float *)self.threshold; }
-
(NSArray *)intArrayToFloatArray:(uint32_t *)array { int count = array.count; NSArray *result = [NSArray array];
for (int i = 0; i < count; i++) { [result addObject:(float)array[i]); }
return result; }
-
(NSArray *)floatArrayToIntegerArray:(double *)array { int count = array.count; NSArray *result = [NSArray array];
for (int i = 0; i < count; i++) { [result addObject:(int)round(array[i] * 255)]; }
return result; }
-
(NSArray *)uniformDistribution { int count = self.pixelValues.count; double *dist = malloc(sizeof(double) * count);
for (int i = 0; i < count; i++) { dist[i] = 1.0 / count; }
return [self floatArrayToIntegerArray:dist]; }
-
(UIImage *)applyOtsuThreshold { if (!self.sourceImage) { return nil; }
int *pixels = (int *)malloc(self.sourceImage.size.width * self.sourceImage.size.height);
for (int y = 0; y < self.sourceImage.size.height; y++) { for (int x = 0; x < self.sourceImage.size.width; x++) { int pixel = [self.sourceImage pixelAtX:x y:y]; int thresholded = (pixel > self.threshold) ? 255 : 0; *pixels + (y * self.sourceImage.size.width + x) = thresholded; } }
free(pixels); return [self.sourceImage applyThresholdWithPixels:pixels]; }
代码解释与改进
1. **类结构设计**:通过创建一个`OtsuAlgorithm`类来封装算法逻辑,确保代码更具可维护性和可扩展性。
2. **属性管理**:通过属性管理像素数据、概率分布等核心数据,保证数据流转透明,易于调试和优化。
3. **性能优化**:通过预先计算像素值和概率分布,避免重复计算,提升算法运行效率。
4. 核心算法改进:在计算累积概率和方差时,采用双精度浮点数计算,减少了精度丢失问题,提升了结果准确性。
5. 代码规范化:严格按照Objective-C编码规范进行编写,包括注释的规范、方法命名的规范等,提高了代码可读性。
应用场景与总结
OTSU算法在图像处理领域有广泛应用,尤其是在医学图像分析、自动驾驶中的物体检测、以及工业检测中的质量控制等场景中。
通过本文中的Objective-C实现,开发者可以轻松在实际项目中应用这一经典算法,实现图像的二值化处理,提升图像质量和准确性。