Le sous-échantillonnage et de Suréchantillonnage pour Gaussien Image Pyramides en Swift
Introduction
je suis intéressé à écrire une fonction qui sort pour moi le prochain niveau dans une pyramide gaussienne(je veux éventuellement arriver à créer une pyramide Laplacienne) pour une utilisation dans le traitement d'image. (Lien pour référence)https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)
Le Sous-Échantillonnage Problème
maintenant la partie facile de ceci est que lorsque vous vers le bas / upsample, un 5-tap le filtre est convolé avec l'image avant le redimensionnement.
cependant, la partie intéressante au sujet de faire des pyramides d'image est que vous devez descendre et monter une image par un facteur de .5 ou 2, selon la direction que vous prenez. Swift a quelques façons de le faire, comme utiliser CIAffineTransform et CILanczosTransform, mais je me demande s'il y a des façons de le faire un peu plus naïvement parce que je ne me soucie pas de la qualité de l'image redimensionnée. Pour ce post, je vais utilisez Lenna (512x512) comme exemple, voir ci-dessous:
Si nous voulons sous-échantillonner une image par un facteur de deux, nous prenons toutes les impairs des données de pixels pour former une nouvelle image. Dans MATLAB, ceci est effectué comme suit (après le flou gaussien):
Si I
est votre image d'entrée et est de taille NxM, avec 3 mappages de couleurs stockées pour P (une matrice 512x512x3), puis l'image décimée par une échelle de .5 est
R = I(1:2:end, 1:2:end,:)
toute la nouvelle image est la précédente avec les colonnes et les lignes impaires numérotées de l'image. On obtient ce qui suit, une photo 256x256 qui est le premier niveau de la pyramide gaussienne:
est-ce une telle chose existe dans swift? Est-ce faisable dans L'image de base, ou peut-être un filtre personnalisé OpenGL?
Le Suréchantillonnage Problème:
le suréchantillonnage est vraiment utilisé uniquement lors de la création d'une Pyramide Laplacienne. Cependant l'idée naïve de le faire est de faire ce qui suit:
Initialiser R
, un contexte d'image vierge de la taille que vous voulez survoler. Dans ce cas, nous serons en train d'échantillonner à la hausse la photo Downsampled Lenna comme vu ci-dessus, donc R
doit être une image vierge 512x512.
ensuite, multipliez les valeurs des pixels de l'image downsampled,I
par 4. Cela peut être fait dans swift en convolvant l'image avec la matrice 3x3 [0,0,0;0,4,0;0,0,0]
. Puis on peut distribuer uniformément les pixels de l'image dans l'image vide, R
. Cela ressemble à:
enfin, on peut utiliser le même flou gaussien de 5 clics sur cette image pour récupérer l'image échantillonnée:
j'aimerais savoir s'il est possible d'utiliser une méthode semblable de suréchantillonnage dans swift.
une autre chose dont je ne suis pas sûr est si il est vraiment important sur la technique de redimensionner une image pour le filtrage gaussien/laplacien. Si non, alors certainement je pourrais juste utiliser la méthode plus rapide Construit Dans que d'essayer de faire le mien.
2 réponses
GPUImage bibliothèque de traitement d' pouvez-vous donner quelques-échantillonnage et, éventuellement, conduire à votre Pyramide Laplacienne.
pod 'GPUImage'
SHARPEN UPSAMPLING:
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 UPSAMPLING:
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;
j'ai fait quelques progrès, et je considère à peu près ceci comme une réponse à ma question, bien que certaines choses soient un peu différentes et je ne pense pas que cette méthode soit très rapide. J'aimerais entendre de personne de voir comment rendre ce code plus rapide. Dans le bas, il semble que redimensionner l'image prend le plus de temps, je reçois une tonne d'appels à la section ovveride outputImage et je n'ai aucune idée de la raison pour laquelle c'est. Malheureusement, quand j'exécute la fonction Laplacian Pyramid ci-dessous, il faut environ 5 secondes pour terminer sur une photo 275x300. Ce n'est pas bon, et je ne sais pas comment l'accélérer. Je soupçonne le filtre resample d'être le coupable. Cependant je ne suis pas assez versé pour savoir comment le rendre plus rapide.
tout d'Abord, les filtres personnalisés:
ce premier redimensionne une image par un simple changement d'échelle. Je pense que c'est la meilleure technique de rééchelonnement dans ce cas parce que tout ce qui est fait est une réplication de pixels lorsqu'ils sont redimensionnés. Par exemple, si nous avons le bloc de pixels suivant et effectuons une échelle 2.0, puis la cartographie ressemble à ce qui suit:
[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ]
(Merci à Simon Gladman pour l'idée)
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
}
}
celui-ci est un simple mélange de différence.
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)
}
}
voici quelques définitions de fonctions:
cette fonction ne fait qu'effectuer un flou gaussien sur l'image, selon le même 5 filtre de robinet que décrit dans le papier de Burt & Adelson. Vous ne savez pas comment se débarrasser de les pixels bordant maladroits qui semblent être supplémentaires.
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)
}
cette fonction simplifie simplement l'utilisation de resample. Vous pouvez spécifier une taille cible de la nouvelle image. Cela s'avère être plus facile à traiter que de définir un paramètre D'échelle 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
}
cette fonction simplifie simplement l'utilisation du filtre de différence. Il suffit de noter que c'est
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
}
cette fonction calcule les dimensions de niveau de chaque pyramide, et les stocke dans un tableau. Utile pour plus tard.
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
}
passons maintenant aux bonnes choses: celle-ci crée une pyramide gaussienne d'un nombre donné de niveaux.
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
}
enfin, cette fonction crée la pyramide Laplacienne avec un nombre donné de niveaux. Notez que dans les deux fonctions pyramidales, chaque niveau est stocké dans un tableau.
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
}