Swift 中高斯图像金字塔的下采样和上采样
Downsampling and Upsampling for Gaussian Image Pyramids in Swift
简介
我有兴趣编写一个函数,为我输出高斯金字塔的下一层(我最终想创建一个拉普拉斯金字塔)用于图像处理。 (Link供参考https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)
下采样问题
现在最简单的部分是,当您 down/upsample 时,一个 5 阶滤波器在调整大小之前与图像卷积。
但是,制作图像金字塔的有趣之处在于,您必须根据要前进的方向,将图像下采样和上采样倍数为 0.5 或 2。 Swift 有几种方法可以做到这一点,例如使用 CIAffineTransform 和 CILanczosTransform,但是我想知道是否有更简单的方法来做到这一点,因为我不关心调整后图像的质量。对于这个 post,我将以 Lenna(512x512) 为例,如下所示:
如果我们想对图像进行二分之一的下采样,我们将采用所有奇数像素数据来形成新图像。在 MATLAB 中,这是按如下方式执行的(在高斯模糊之后):
如果 I
是您的输入图像并且大小为 NxM,为 P(512x512x3 矩阵)存储了 3 种颜色映射,则按 0.5 比例抽取的图像是
R = I(1:2:end, 1:2:end,:)
所有新图像都是以前的图像的奇数列和行。这会产生以下内容,即高斯金字塔第一层的 256x256 照片:
swift有这样的东西吗?在 Core Image 或 OpenGL 自定义滤镜中是否可行?
上采样问题:
上采样实际上只在创建拉普拉斯金字塔时使用。然而,这样做的天真想法是执行以下操作:
初始化 R
,您要上采样到的大小的空白图像上下文。在这种情况下,我们将如上所示对下采样的 Lenna 照片进行上采样,因此 R
必须是 512x512 空白图像。
接下来,将下采样图像的像素值 I
乘以 4。这可以在 swift 中通过将图像与 3x3 矩阵 [0,0,0;0,4,0;0,0,0]
进行卷积来完成。然后可以将图像的像素均匀分布到更大的空白图像中,R
。这看起来像:
最后,可以在此图像上使用相同的 5 抽头高斯模糊来恢复上采样图像:
我想知道是否可以在 swift 中采用类似的上采样方法。
我不确定的另一件事是调整图像大小以进行 gaussian/laplacian 过滤的技术是否真的很重要。如果没有,那么我当然可以使用最快的内置方法而不是尝试自己制作。
GPUImage processing library 可以给你一些上采样,并可能导致你的 拉普拉斯金字塔。
pod 'GPUImage'
锐化上采样:
UIImage *inputImage = [UIImage imageNamed:@"cutelady"];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc]initWithImage:inputImage];
GPUImageSharpenFilter *stillImageFilter = [[GPUImageSharpenFilter alloc] init];
[stillImageSource addTarget:stillImageFilter];
[stillImageFilter useNextFrameForImageCapture];
[stillImageSource processImage];
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer];
LANCZOS 上采样:
UIImage *inputImage = [UIImage imageNamed:@"cutelady"];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage];
GPUImageLanczosResamplingFilter *stillImageFilter = [[GPUImageLanczosResamplingFilter alloc] init];
[stillImageSource addTarget:stillImageFilter];
[stillImageFilter useNextFrameForImageCapture];
[stillImageSource processImage];
[stillImageSource forceProcessingAtSizeRespectingAspectRatio:CGSizeMake(200, 200)];
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer];
cell.imageView.image = currentFilteredVideoFrame;
我取得了一些进展,我几乎认为这是对我的问题的回答,尽管有些事情有点不同,而且我认为这种方法不是很快。我很想听听任何人如何使这段代码更快。在下面,调整图像大小似乎占用了最多的时间,我收到了大量对 ovveride outputImage 部分的调用,但我不知道为什么会这样。不幸的是,当我执行下面的 运行 拉普拉斯金字塔函数时,完成一张 275x300 照片大约需要 5 秒。这一点都不好,我对如何加快速度有点不知所措。我怀疑重采样过滤器是罪魁祸首。但是我不够精通,不知道如何让它更快。
首先,自定义过滤器:
第一个通过简单的重新缩放来调整图像的大小。我认为这是在这种情况下重新缩放的最佳技术,因为所做的只是在调整大小时复制像素。例如,如果我们有以下像素块并执行 2.0 比例,则映射如下所示:
[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ]
(感谢 Simon Gladman 的想法)
public class ResampleFilter: CIFilter
{
var inputImage : CIImage?
var inputScaleX: CGFloat = 1
var inputScaleY: CGFloat = 1
let warpKernel = CIWarpKernel(string:
"kernel vec2 resample(float inputScaleX, float inputScaleY)" +
" { " +
" float y = (destCoord().y / inputScaleY); " +
" float x = (destCoord().x / inputScaleX); " +
" return vec2(x,y); " +
" } "
)
override public var outputImage: CIImage!
{
if let inputImage = inputImage,
kernel = warpKernel
{
let arguments = [inputScaleX, inputScaleY]
let extent = CGRect(origin: inputImage.extent.origin,
size: CGSize(width: inputImage.extent.width*inputScaleX,
height: inputImage.extent.height*inputScaleY))
return kernel.applyWithExtent(extent,
roiCallback:
{
(index,rect) in
let sampleX = rect.origin.x/self.inputScaleX
let sampleY = rect.origin.y/self.inputScaleY
let sampleWidth = rect.width/self.inputScaleX
let sampleHeight = rect.height/self.inputScaleY
let sampleRect = CGRect(x: sampleX, y: sampleY, width: sampleWidth, height: sampleHeight)
return sampleRect
},
inputImage : inputImage,
arguments : arguments)
}
return nil
}
}
这是一个简单的差异混合。
public class DifferenceOfImages: CIFilter
{
var inputImage1 : CIImage? //Initializes input
var inputImage2 : CIImage?
var kernel = CIKernel(string: //The actual custom kernel code
"kernel vec4 Difference(__sample image1,__sample image2)" +
" { " +
" float colorR = image1.r - image2.r; " +
" float colorG = image1.g - image2.g; " +
" float colorB = image1.b - image2.b; " +
" return vec4(colorR,colorG,colorB,1); " +
" } "
)
var extentFunction: (CGRect, CGRect) -> CGRect =
{ (a: CGRect, b: CGRect) in return CGRectZero }
override public var outputImage: CIImage!
{
guard let inputImage1 = inputImage1,
inputImage2 = inputImage2,
kernel = kernel
else
{
return nil
}
//apply to whole image
let extent = extentFunction(inputImage1.extent,inputImage2.extent)
//arguments of the kernel
let arguments = [inputImage1,inputImage2]
//return the rectangle that defines the part of the image that CI needs to render rect in the output
return kernel.applyWithExtent(extent,
roiCallback:
{ (index, rect) in
return rect
},
arguments: arguments)
}
}
现在进行一些函数定义:
根据 Burt & Adelson 的论文中描述的相同 5 抽头滤波器,此函数仅对图像执行高斯模糊。不确定如何摆脱看起来多余的尴尬边界像素。
public func GaussianFilter(ciImage: CIImage) -> CIImage
{
//5x5 convolution to image
let kernelValues: [CGFloat] = [
0.0025, 0.0125, 0.0200, 0.0125, 0.0025,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0200, 0.1000, 0.1600, 0.1000, 0.0200,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0025, 0.0125, 0.0200, 0.0125, 0.0025 ]
let weightMatrix = CIVector(values: kernelValues,
count: kernelValues.count)
let filter = CIFilter(name: "CIConvolution5X5",
withInputParameters: [
kCIInputImageKey: ciImage,
kCIInputWeightsKey: weightMatrix])!
let final = filter.outputImage!
let rect = CGRect(x: 0, y: 0, width: ciImage.extent.size.width, height: ciImage.extent.size.height)
return final.imageByCroppingToRect(rect)
}
这个函数只是简化了resample的使用。您可以指定新图像的目标大小。事实证明,这比设置比例参数 IMO 更容易处理。
public func resampleImage(inputImage: CIImage, sizeX: CGFloat, sizeY: CGFloat) -> CIImage
{
let inputWidth : CGFloat = inputImage.extent.size.width
let inputHeight : CGFloat = inputImage.extent.size.height
let scaleX = sizeX/inputWidth
let scaleY = sizeY/inputHeight
let resamplefilter = ResampleFilter()
resamplefilter.inputImage = inputImage
resamplefilter.inputScaleX = scaleX
resamplefilter.inputScaleY = scaleY
return resamplefilter.outputImage
}
这个功能只是简化了差分滤镜的使用。请注意它是
imageOne - ImageTwo
.
public func Difference(imageOne:CIImage,imageTwo:CIImage) -> CIImage
{
let generalFilter = DifferenceOfImages()
generalFilter.inputImage1 = imageOne
generalFilter.inputImage2 = imageTwo
generalFilter.extentFunction = { (fore, back) in return back.union(fore)}
return generalFilter.outputImage
}
此函数计算每个金字塔的级别维度,并将它们存储在数组中。以后有用。
public func LevelDimensions(image: CIImage,levels:Int) -> [[CGFloat]]
{
let inputWidth : CGFloat = image.extent.width
let inputHeight : CGFloat = image.extent.height
var levelSizes : [[CGFloat]] = [[inputWidth,inputHeight]]
for j in 1...(levels-1)
{
let temp = [floor(inputWidth/pow(2.0,CGFloat(j))),floor(inputHeight/pow(2,CGFloat(j)))]
levelSizes.append(temp)
}
return levelSizes
}
现在开始介绍好东西:这会创建一个给定级别数的高斯金字塔。
public func GaussianPyramid(image: CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels: levels)
var GauPyr : [CIImage] = [image]
var I : CIImage
var J : CIImage
for j in 1 ... levels-1
{
J = GaussianFilter(GauPyr[j-1])
I = resampleImage(J, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1])
GauPyr.append(I)
}
return GauPyr
}
最后,此函数创建具有给定层数的拉普拉斯金字塔。请注意,在两个 Pyramid 函数中,每个级别都存储在一个数组中。
public func LaplacianPyramid(image:CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels:levels)
var LapPyr : [CIImage] = []
var I : CIImage
var J : CIImage
J = image
for j in 0 ... levels-2
{
let blur = GaussianFilter(J)
I = resampleImage(blur, sizeX: PyrLevel[j+1][0], sizeY: PyrLevel[j+1][1])
let diff = Difference(J,imageTwo: resampleImage(I, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1]))
LapPyr.append(diff)
J = I
}
LapPyr.append(J)
return LapPyr
}
简介
我有兴趣编写一个函数,为我输出高斯金字塔的下一层(我最终想创建一个拉普拉斯金字塔)用于图像处理。 (Link供参考https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)
下采样问题
现在最简单的部分是,当您 down/upsample 时,一个 5 阶滤波器在调整大小之前与图像卷积。
但是,制作图像金字塔的有趣之处在于,您必须根据要前进的方向,将图像下采样和上采样倍数为 0.5 或 2。 Swift 有几种方法可以做到这一点,例如使用 CIAffineTransform 和 CILanczosTransform,但是我想知道是否有更简单的方法来做到这一点,因为我不关心调整后图像的质量。对于这个 post,我将以 Lenna(512x512) 为例,如下所示:
如果我们想对图像进行二分之一的下采样,我们将采用所有奇数像素数据来形成新图像。在 MATLAB 中,这是按如下方式执行的(在高斯模糊之后):
如果 I
是您的输入图像并且大小为 NxM,为 P(512x512x3 矩阵)存储了 3 种颜色映射,则按 0.5 比例抽取的图像是
R = I(1:2:end, 1:2:end,:)
所有新图像都是以前的图像的奇数列和行。这会产生以下内容,即高斯金字塔第一层的 256x256 照片:
swift有这样的东西吗?在 Core Image 或 OpenGL 自定义滤镜中是否可行?
上采样问题:
上采样实际上只在创建拉普拉斯金字塔时使用。然而,这样做的天真想法是执行以下操作:
初始化 R
,您要上采样到的大小的空白图像上下文。在这种情况下,我们将如上所示对下采样的 Lenna 照片进行上采样,因此 R
必须是 512x512 空白图像。
接下来,将下采样图像的像素值 I
乘以 4。这可以在 swift 中通过将图像与 3x3 矩阵 [0,0,0;0,4,0;0,0,0]
进行卷积来完成。然后可以将图像的像素均匀分布到更大的空白图像中,R
。这看起来像:
最后,可以在此图像上使用相同的 5 抽头高斯模糊来恢复上采样图像:
我想知道是否可以在 swift 中采用类似的上采样方法。
我不确定的另一件事是调整图像大小以进行 gaussian/laplacian 过滤的技术是否真的很重要。如果没有,那么我当然可以使用最快的内置方法而不是尝试自己制作。
GPUImage processing library 可以给你一些上采样,并可能导致你的 拉普拉斯金字塔。
pod 'GPUImage'
锐化上采样:
UIImage *inputImage = [UIImage imageNamed:@"cutelady"];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc]initWithImage:inputImage];
GPUImageSharpenFilter *stillImageFilter = [[GPUImageSharpenFilter alloc] init];
[stillImageSource addTarget:stillImageFilter];
[stillImageFilter useNextFrameForImageCapture];
[stillImageSource processImage];
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer];
LANCZOS 上采样:
UIImage *inputImage = [UIImage imageNamed:@"cutelady"];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage];
GPUImageLanczosResamplingFilter *stillImageFilter = [[GPUImageLanczosResamplingFilter alloc] init];
[stillImageSource addTarget:stillImageFilter];
[stillImageFilter useNextFrameForImageCapture];
[stillImageSource processImage];
[stillImageSource forceProcessingAtSizeRespectingAspectRatio:CGSizeMake(200, 200)];
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer];
cell.imageView.image = currentFilteredVideoFrame;
我取得了一些进展,我几乎认为这是对我的问题的回答,尽管有些事情有点不同,而且我认为这种方法不是很快。我很想听听任何人如何使这段代码更快。在下面,调整图像大小似乎占用了最多的时间,我收到了大量对 ovveride outputImage 部分的调用,但我不知道为什么会这样。不幸的是,当我执行下面的 运行 拉普拉斯金字塔函数时,完成一张 275x300 照片大约需要 5 秒。这一点都不好,我对如何加快速度有点不知所措。我怀疑重采样过滤器是罪魁祸首。但是我不够精通,不知道如何让它更快。
首先,自定义过滤器:
第一个通过简单的重新缩放来调整图像的大小。我认为这是在这种情况下重新缩放的最佳技术,因为所做的只是在调整大小时复制像素。例如,如果我们有以下像素块并执行 2.0 比例,则映射如下所示:
[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ]
(感谢 Simon Gladman 的想法)
public class ResampleFilter: CIFilter
{
var inputImage : CIImage?
var inputScaleX: CGFloat = 1
var inputScaleY: CGFloat = 1
let warpKernel = CIWarpKernel(string:
"kernel vec2 resample(float inputScaleX, float inputScaleY)" +
" { " +
" float y = (destCoord().y / inputScaleY); " +
" float x = (destCoord().x / inputScaleX); " +
" return vec2(x,y); " +
" } "
)
override public var outputImage: CIImage!
{
if let inputImage = inputImage,
kernel = warpKernel
{
let arguments = [inputScaleX, inputScaleY]
let extent = CGRect(origin: inputImage.extent.origin,
size: CGSize(width: inputImage.extent.width*inputScaleX,
height: inputImage.extent.height*inputScaleY))
return kernel.applyWithExtent(extent,
roiCallback:
{
(index,rect) in
let sampleX = rect.origin.x/self.inputScaleX
let sampleY = rect.origin.y/self.inputScaleY
let sampleWidth = rect.width/self.inputScaleX
let sampleHeight = rect.height/self.inputScaleY
let sampleRect = CGRect(x: sampleX, y: sampleY, width: sampleWidth, height: sampleHeight)
return sampleRect
},
inputImage : inputImage,
arguments : arguments)
}
return nil
}
}
这是一个简单的差异混合。
public class DifferenceOfImages: CIFilter
{
var inputImage1 : CIImage? //Initializes input
var inputImage2 : CIImage?
var kernel = CIKernel(string: //The actual custom kernel code
"kernel vec4 Difference(__sample image1,__sample image2)" +
" { " +
" float colorR = image1.r - image2.r; " +
" float colorG = image1.g - image2.g; " +
" float colorB = image1.b - image2.b; " +
" return vec4(colorR,colorG,colorB,1); " +
" } "
)
var extentFunction: (CGRect, CGRect) -> CGRect =
{ (a: CGRect, b: CGRect) in return CGRectZero }
override public var outputImage: CIImage!
{
guard let inputImage1 = inputImage1,
inputImage2 = inputImage2,
kernel = kernel
else
{
return nil
}
//apply to whole image
let extent = extentFunction(inputImage1.extent,inputImage2.extent)
//arguments of the kernel
let arguments = [inputImage1,inputImage2]
//return the rectangle that defines the part of the image that CI needs to render rect in the output
return kernel.applyWithExtent(extent,
roiCallback:
{ (index, rect) in
return rect
},
arguments: arguments)
}
}
现在进行一些函数定义:
根据 Burt & Adelson 的论文中描述的相同 5 抽头滤波器,此函数仅对图像执行高斯模糊。不确定如何摆脱看起来多余的尴尬边界像素。
public func GaussianFilter(ciImage: CIImage) -> CIImage
{
//5x5 convolution to image
let kernelValues: [CGFloat] = [
0.0025, 0.0125, 0.0200, 0.0125, 0.0025,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0200, 0.1000, 0.1600, 0.1000, 0.0200,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0025, 0.0125, 0.0200, 0.0125, 0.0025 ]
let weightMatrix = CIVector(values: kernelValues,
count: kernelValues.count)
let filter = CIFilter(name: "CIConvolution5X5",
withInputParameters: [
kCIInputImageKey: ciImage,
kCIInputWeightsKey: weightMatrix])!
let final = filter.outputImage!
let rect = CGRect(x: 0, y: 0, width: ciImage.extent.size.width, height: ciImage.extent.size.height)
return final.imageByCroppingToRect(rect)
}
这个函数只是简化了resample的使用。您可以指定新图像的目标大小。事实证明,这比设置比例参数 IMO 更容易处理。
public func resampleImage(inputImage: CIImage, sizeX: CGFloat, sizeY: CGFloat) -> CIImage
{
let inputWidth : CGFloat = inputImage.extent.size.width
let inputHeight : CGFloat = inputImage.extent.size.height
let scaleX = sizeX/inputWidth
let scaleY = sizeY/inputHeight
let resamplefilter = ResampleFilter()
resamplefilter.inputImage = inputImage
resamplefilter.inputScaleX = scaleX
resamplefilter.inputScaleY = scaleY
return resamplefilter.outputImage
}
这个功能只是简化了差分滤镜的使用。请注意它是
imageOne - ImageTwo
.
public func Difference(imageOne:CIImage,imageTwo:CIImage) -> CIImage
{
let generalFilter = DifferenceOfImages()
generalFilter.inputImage1 = imageOne
generalFilter.inputImage2 = imageTwo
generalFilter.extentFunction = { (fore, back) in return back.union(fore)}
return generalFilter.outputImage
}
此函数计算每个金字塔的级别维度,并将它们存储在数组中。以后有用。
public func LevelDimensions(image: CIImage,levels:Int) -> [[CGFloat]]
{
let inputWidth : CGFloat = image.extent.width
let inputHeight : CGFloat = image.extent.height
var levelSizes : [[CGFloat]] = [[inputWidth,inputHeight]]
for j in 1...(levels-1)
{
let temp = [floor(inputWidth/pow(2.0,CGFloat(j))),floor(inputHeight/pow(2,CGFloat(j)))]
levelSizes.append(temp)
}
return levelSizes
}
现在开始介绍好东西:这会创建一个给定级别数的高斯金字塔。
public func GaussianPyramid(image: CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels: levels)
var GauPyr : [CIImage] = [image]
var I : CIImage
var J : CIImage
for j in 1 ... levels-1
{
J = GaussianFilter(GauPyr[j-1])
I = resampleImage(J, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1])
GauPyr.append(I)
}
return GauPyr
}
最后,此函数创建具有给定层数的拉普拉斯金字塔。请注意,在两个 Pyramid 函数中,每个级别都存储在一个数组中。
public func LaplacianPyramid(image:CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels:levels)
var LapPyr : [CIImage] = []
var I : CIImage
var J : CIImage
J = image
for j in 0 ... levels-2
{
let blur = GaussianFilter(J)
I = resampleImage(blur, sizeX: PyrLevel[j+1][0], sizeY: PyrLevel[j+1][1])
let diff = Difference(J,imageTwo: resampleImage(I, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1]))
LapPyr.append(diff)
J = I
}
LapPyr.append(J)
return LapPyr
}