Objective C中iOS的图像模糊检测

Posted

技术标签:

【中文标题】Objective C中iOS的图像模糊检测【英文标题】:Image blur detection for iOS in Objective C 【发布时间】:2021-04-26 07:36:11 【问题描述】:

我正在尝试确定从相机拍摄的 ios 图像是否模糊。 我已经在拍照前检查了相机焦距,但是如果图像模糊,这似乎有所不同。

我使用 Open CV 在 android 上完成了这项工作, OpenCV with Laplacian formula to detect image is blur or not in Android

结果是,

int soglia = -6118750;
if (maxLap <= soglia)  // blurry

我玩了一下这个并降低到-6718750。

对于 iOS,关于执行此操作的信息似乎较少。我看到一些人试图在 iOS 上使用 Open CV 来解决这个问题,但他们似乎并不成功。

我看到这篇文章在 iOS 上使用 Metal 来做到这一点, https://medium.com/better-programming/blur-detection-via-metal-on-ios-16dd02cb1558

这是用 Swift 编写的,所以我手动将其逐行转换为 Objective C。 我认为可能代码是正确的翻译,但不确定原始代码是否正确或一般适用于相机捕获的图像?

在我的测试中,它总是给我一个 2 的结果,无论是平均值还是方差,这如何用于检测模糊图像或任何其他想法?

- (BOOL) detectBlur: (CGImageRef)image 
NSLog(@"detectBlur: %@", image);
// Initialize MTL
device = MTLCreateSystemDefaultDevice();
queue = [device newCommandQueue];

// Create a command buffer for the transformation pipeline
id <MTLCommandBuffer> commandBuffer = [queue commandBuffer];
// These are the two built-in shaders we will use
MPSImageLaplacian* laplacian = [[MPSImageLaplacian alloc] initWithDevice: device];
MPSImageStatisticsMeanAndVariance* meanAndVariance = [[MPSImageStatisticsMeanAndVariance alloc] initWithDevice: device];
// Load the captured pixel buffer as a texture
MTKTextureLoader* textureLoader = [[MTKTextureLoader alloc] initWithDevice: device];
id <MTLTexture> sourceTexture = [textureLoader newTextureWithCGImage: image options: nil error: nil];
// Create the destination texture for the laplacian transformation
MTLTextureDescriptor* lapDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: sourceTexture.pixelFormat width: sourceTexture.width height: sourceTexture.height mipmapped: false];
lapDesc.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
id <MTLTexture> lapTex = [device newTextureWithDescriptor: lapDesc];

// Encode this as the first transformation to perform
[laplacian encodeToCommandBuffer: commandBuffer sourceTexture: sourceTexture destinationTexture: lapTex];
// Create the destination texture for storing the variance.
MTLTextureDescriptor* varianceTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: sourceTexture.pixelFormat width: 2 height: 1 mipmapped: false];
varianceTextureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
id <MTLTexture> varianceTexture = [device newTextureWithDescriptor: varianceTextureDescriptor];
// Encode this as the second transformation
[meanAndVariance encodeToCommandBuffer: commandBuffer sourceTexture: lapTex destinationTexture: varianceTexture];
// Run the command buffer on the GPU and wait for the results
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
// The output will be just 2 pixels, one with the mean, the other the variance.
NSMutableData* result = [NSMutableData dataWithLength: 2];
void* resultBytes = result.mutableBytes;
//var result = [Int8](repeatElement(0, count: 2));
MTLRegion region = MTLRegionMake2D(0, 0, 2, 1);
const char* bytes = resultBytes;
NSLog(@"***resultBytes: %d", bytes[0]);
NSLog(@"***resultBytes: %d", bytes[1]);
[varianceTexture getBytes: resultBytes bytesPerRow: 1 * 2 * 4 fromRegion: region mipmapLevel: 0];
NSLog(@"resultBytes: %d", bytes[0]);
NSLog(@"resultBytes: %d", bytes[1]);

int variance = (int)bytes[1];

return variance < 2;

【问题讨论】:

这是一种模糊检测方法:***.com/questions/60587428/… 然而,答案是用 C++ 实现的,你可能会发现它很有用。 谢谢,但是上面的 iOS Metal 代码有什么输入吗?它似乎给了我一个值 1-5,如果模糊,通常为 2,如果不是,则为 2-5,但我想我需要更精细的粒度,为什么上述代码的方差如此之低?您的链接似乎给出了 0-500 的差异 能写出如此精细的快速版本吗? 【参考方案1】:

您的代码暗示您假设一个带有 4 个通道的变量纹理,每个通道一个字节。但是对于您的 varianceTextureDescriptor,您可能希望使用浮点值,这也是由于方差的值范围,请参见下面的代码。此外,您似乎想与 OpenCV 进行比较并具有可比较的值。

无论如何,让我们从 Apple 的 MPSImageLaplacian 文档开始:

此过滤器使用具有 3x3 内核的优化卷积过滤器,其权重如下:

在 Python 中可以这样做,例如喜欢:

import cv2
import np
from PIL import Image

img = np.array(Image.open('forrest.jpg'))
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
laplacian_kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])

print(img.dtype)
print(img.shape)

laplacian = cv2.filter2D(img, -1, laplacian_kernel)
print('mean', np.mean(laplacian))
print('variance', np.var(laplacian, axis=(0, 1)))

cv2.imshow('laplacian', laplacian)
key = cv2.waitKey(0)

请注意,我们完全使用 Apple 文档中给出的值。

它为我的测试图像提供以下输出:

uint8
(4032, 3024)
mean 14.531123203525237
variance 975.6843631756923

MPSImageStatisticsMeanAndVariance

我们现在希望使用 Apple 的 Metal Performance Shader MPSImageStatisticsMeanAndVariance 获得相同的值。

将输入图像转换为灰度图像很有用。然后应用 MPSImageLaplacian 图像内核。

一个字节也只能有 0 到 255 之间的值。因此,对于得到的均值或方差值,我们希望有浮点值。我们可以独立于输入图像的像素格式来指定它。所以我们应该如下使用MTLPixelFormatR32Float:

 MTLTextureDescriptor *varianceTextureDescriptor = [MTLTextureDescriptor
                                                       texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float
                                                       width:2
                                                       height:1
                                                       mipmapped:NO];

然后我们想将结果纹理中的 8 个字节解释为两个浮点数。我们可以通过工会很好地做到这一点。这可能如下所示:

union 
  float f[2];
  unsigned char bytes[8];
 u1;
MTLRegion region = MTLRegionMake2D(0, 0, 2, 1);
[varianceTexture getBytes:u1.bytes bytesPerRow:2 * 4 fromRegion:region mipmapLevel: 0];

最后,我们需要知道计算是使用介于 0 和 1 之间的浮点值完成的,这实际上意味着我们要乘以 255 或 255*255 以获得方差以使其进入可比较的值范围:

NSLog(@"mean: %f", u1.f[0] * 255);
NSLog(@"variance: %f", u1.f[1] * 255 * 255);

为了完整起见,整个Objective-C代码:

id<MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> queue = [device newCommandQueue];
id<MTLCommandBuffer> commandBuffer = [queue commandBuffer];

MTKTextureLoader *textureLoader = [[MTKTextureLoader alloc] initWithDevice:device];
id<MTLTexture> sourceTexture = [textureLoader newTextureWithCGImage:image.CGImage options:nil error:nil];


CGColorSpaceRef srcColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceRef dstColorSpace = CGColorSpaceCreateDeviceGray();
CGColorConversionInfoRef conversionInfo = CGColorConversionInfoCreate(srcColorSpace, dstColorSpace);
MPSImageConversion *conversion = [[MPSImageConversion alloc] initWithDevice:device
                                                                   srcAlpha:MPSAlphaTypeAlphaIsOne
                                                                  destAlpha:MPSAlphaTypeAlphaIsOne
                                                            backgroundColor:nil
                                                             conversionInfo:conversionInfo];
MTLTextureDescriptor *grayTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR16Unorm
                                                                                                 width:sourceTexture.width
                                                                                                height:sourceTexture.height
                                                                                             mipmapped:false];
grayTextureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
id<MTLTexture> grayTexture = [device newTextureWithDescriptor:grayTextureDescriptor];
[conversion encodeToCommandBuffer:commandBuffer sourceTexture:sourceTexture destinationTexture:grayTexture];


MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:grayTexture.pixelFormat
                                                                                             width:sourceTexture.width
                                                                                            height:sourceTexture.height
                                                                                         mipmapped:false];
textureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
id<MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor];

MPSImageLaplacian *imageKernel = [[MPSImageLaplacian alloc] initWithDevice:device];
[imageKernel encodeToCommandBuffer:commandBuffer sourceTexture:grayTexture destinationTexture:texture];


MPSImageStatisticsMeanAndVariance *meanAndVariance = [[MPSImageStatisticsMeanAndVariance alloc] initWithDevice:device];
MTLTextureDescriptor *varianceTextureDescriptor = [MTLTextureDescriptor
                                                   texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float
                                                   width:2
                                                   height:1
                                                   mipmapped:NO];
varianceTextureDescriptor.usage = MTLTextureUsageShaderWrite;
id<MTLTexture> varianceTexture = [device newTextureWithDescriptor:varianceTextureDescriptor];
[meanAndVariance encodeToCommandBuffer:commandBuffer sourceTexture:texture destinationTexture:varianceTexture];


[commandBuffer commit];
[commandBuffer waitUntilCompleted];

union 
    float f[2];
    unsigned char bytes[8];
 u;

MTLRegion region = MTLRegionMake2D(0, 0, 2, 1);
[varianceTexture getBytes:u.bytes bytesPerRow:2 * 4 fromRegion:region mipmapLevel: 0];

NSLog(@"mean: %f", u.f[0] * 255);
NSLog(@"variance: %f", u.f[1] * 255 * 255);

最终输出给出了与 Python 程序相似的值:

mean: 14.528159
variance: 974.630615

Python 代码和 Objective-C 代码也会为其他图像计算类似的值。

即使没有直接询问,也应该注意方差值当然也非常依赖于主题。如果您有一系列具有相同主题的图像,那么该值肯定是有意义的。为了说明这一点,这里有两个不同的主题的小测试,它们都很清晰,但在方差值上显示出明显的差异:

在上部区域中,您可以看到相应的图像在应用拉普拉斯滤波器后转换为灰色,而在下部区域中。相应的中值或方差值可以在图像的中间看到。

【讨论】:

我试过你的代码,但它不起作用。它崩溃了,[meanAndVariance encodeToCommandBuffer:commandBuffer sourceTexture:texture destinationTexture:varianceTexture]; 在 ___lldb_unnamed_symbol389$$MPSImage () 中的 #8 0x00000001d4a95c94 带有 SIGABRT 在我看到的日志中,detectBlur: (DP) (kCGColorSpaceDeviceRGB)> width = 640, height = 480, bpc = 8, bpp = 32, row bytes = 2560 kCGImageAlphaPremultipliedLast | 0(默认字节顺序)| kCGImagePixelFormatPacked 是掩码吗?不,有遮蔽色吗?不,有软面膜吗?不,有哑光吗?不,应该插值吗?是的 /Library/Caches/com.apple.xbs/Sources/MetalImage/MetalImage-124.2/MPSImage/Filters/MPSStatistics.mm,第 752 行:错误“目标 0x2803281c0 纹理应具有与源 0x280351a00 tetxure '/Library/Caches/com.apple.xbs/Sources/MetalImage/MetalImage-124.2/MPSImage/Filters/MPSStatistics.mm:752: 断言失败`目标 0x2803281c0 纹理应该具有与源 0x280351a00 tetxure 相同数量的通道/跨度> 代码假设灰度图像有一个(!)通道(这通常是有道理的,因为您希望有一个数字作为结果来评估模糊度)。【参考方案2】:

Apple 提供了一个示例项目,展示了如何使用 Accelerate 框架find the sharpest image in a sequence of captured images。这可能是一个有用的起点。

【讨论】:

谢谢你的链接,上面的代码有没有cmets?【参考方案3】:

斯威夫特版本

需要记住的一些事情。内核大小为 3x3 (MPSImageLaplacian) 的拉普拉斯函数可以根据图像的大小给出一些不同的结果。如果您要比较相似的图像以获得一致的结果,您可能需要将图像缩放为恒定大小。

guard let image = uiImage.cgImage, let device = MTLCreateSystemDefaultDevice(), let queue = device.makeCommandQueue(), let commandBuffer = queue.makeCommandBuffer() else 
    return


let textureLoader = MTKTextureLoader(device: device)

let sourceColorSpace = CGColorSpaceCreateDeviceRGB()
let destinationColorSpace = CGColorSpaceCreateDeviceGray()
let conversionInfo = CGColorConversionInfo(src: sourceColorSpace, dst: destinationColorSpace)
let conversion = MPSImageConversion(device: device, srcAlpha: MPSAlphaType.alphaIsOne, destAlpha: MPSAlphaType.alphaIsOne, backgroundColor: nil, conversionInfo: conversionInfo)

guard let sourceTexture = try? textureLoader.newTexture(cgImage: image, options: [.origin: MTKTextureLoader.Origin.flippedVertically]) else 
    return


let grayscaleTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: MTLPixelFormat.r16Snorm, width: sourceTexture.width, height: sourceTexture.height, mipmapped: false)
grayscaleTextureDescriptor.usage = [.shaderWrite, .shaderRead]

guard let grayscaleTexture = device.makeTexture(descriptor: grayscaleTextureDescriptor) else 
    return


conversion.encode(commandBuffer: commandBuffer, sourceTexture: sourceTexture, destinationTexture: grayscaleTexture)
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: grayscaleTexture.pixelFormat, width: sourceTexture.width, height: sourceTexture.height, mipmapped: false)
textureDescriptor.usage = [.shaderWrite, .shaderRead]

guard let texture = device.makeTexture(descriptor: textureDescriptor) else 
    return


let laplacian = MPSImageLaplacian(device: device)
laplacian.encode(commandBuffer: commandBuffer, sourceTexture: grayscaleTexture, destinationTexture: texture)
let meanAndVariance = MPSImageStatisticsMeanAndVariance(device: device)
let varianceTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: MTLPixelFormat.r32Float, width: 2, height: 1, mipmapped: false)
varianceTextureDescriptor.usage = [.shaderWrite]

guard let varianceTexture = device.makeTexture(descriptor: varianceTextureDescriptor) else 
    return


meanAndVariance.encode(commandBuffer: commandBuffer, sourceTexture: texture, destinationTexture: varianceTexture)

commandBuffer.commit()
commandBuffer.waitUntilCompleted()
            
var bytes = [Int8](repeatElement(0, count: 8))
let region = MTLRegionMake2D(0, 0, 2, 1)
varianceTexture.getBytes(&bytes, bytesPerRow: 4 * 2, from: region, mipmapLevel: 0)

var result = [Float32](repeating: 0, count: 2)
memcpy(&result, &bytes, 8)

let mean = Double(result[0] * 255.0)
let variance = Double(result[1] * 255.0 * 255.0)
let standardDeviation = sqrt(variance)

【讨论】:

以上是关于Objective C中iOS的图像模糊检测的主要内容,如果未能解决你的问题,请参考以下文章

从 iOS 中的共享按钮启动应用程序 - Objective C 和 React Native

如何在 iOS Swift 或 Objective C 中检测 iPhone 卷访问 [关闭]

obj -c IOS检测大写锁定按键事件

当我超出父图像的范围时,iOS/Objective C 图像 alpha 低

Objective C iOS简单图像旋转类

Objective C - 如何检测用户点击了不允许访问照片按钮