Skip to content

Commit

Permalink
GaussianBlur and ColorJitter ops. (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
barucden committed Jul 12, 2021
1 parent e389e29 commit a121ac9
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 3 deletions.
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MLDataPattern = "9920b226-0b2a-5f5f-9153-9aa70a013f8b"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
ComputationalResources = "0.3"
Expand All @@ -37,11 +38,12 @@ julia = "1"
[extras]
ImageDistances = "51556ac3-7006-55f5-8cb3-34580c88182d"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
ImageQualityIndexes = "2996bd0c-7a13-11e9-2da2-2f5ce47296a9"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"

[targets]
test = ["ImageDistances", "ImageMagick", "Random", "ReferenceTests", "Statistics", "TestImages", "Test"]
test = ["ImageDistances", "ImageMagick", "ImageQualityIndexes", "Random", "ReferenceTests", "Statistics", "TestImages", "Test"]
6 changes: 6 additions & 0 deletions docs/operations/blur/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "Blurring",
"order": [
"gaussianblur.jl"
]
}
34 changes: 34 additions & 0 deletions docs/operations/blur/gaussianblur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ---
# title: GaussianBlur
# cover: gaussianblur.gif
# description: blur the input image using a gaussian kernel
# ---

# [`GaussianBlur`](@ref) can be used to blur the input image using a gaussian
# kernel with a specified kernel size and standard deviation.

using Augmentor
using ImageShow, ImageCore

img_in = testpattern(RGB, ratio=0.5)

mosaicview(
img_in,
augment(img_in, GaussianBlur(3)),
augment(img_in, GaussianBlur(5, 2.5));
fillvalue=colorant"white", nrow=1, npad=10
)

# ## References

#md # ```@docs
#md # GaussianBlur
#md # ```


## save covers #src
using ImageMagick #src
using FileIO #src
include(joinpath("..", "assets", "utilities.jl")) #src
cover = make_gif(testpattern(RGB, ratio=0.5), GaussianBlur(5), 2) #src
ImageMagick.save("gaussianblur.gif", cover; fps=1) #src
32 changes: 32 additions & 0 deletions docs/operations/color/colorjitter.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ---
# title: ColorJitter
# cover: colorjitter.gif
# description: Adjust contrast and brightness of an image
# ---

# [`ColorJitter`](@ref) can be used to adjust the contrast and brightness of an input image.

using Augmentor
using ImageShow, ImageCore

img_in = testpattern(RGB, ratio=0.5)

mosaicview(
img_in,
augment(img_in, ColorJitter(1.2, 0.3)),
augment(img_in, ColorJitter(0.75, -0.2));
fillvalue=colorant"white", nrow=1, npad=10
)

# ## References

#md # ```@docs
#md # ColorJitter
#md # ```

## save covers #src
using ImageMagick #src
using FileIO #src
include(joinpath("..", "assets", "utilities.jl")) #src
cover = make_gif(testpattern(RGB, ratio=0.5), ColorJitter(), 4) #src
ImageMagick.save("colorjitter.gif", cover; fps=1) #src
7 changes: 7 additions & 0 deletions docs/operations/color/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"title": "Color adjustments",
"order": [
"colorjitter.jl"
],
"description": ""
}
4 changes: 3 additions & 1 deletion docs/operations/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"order": [
"affine",
"distortions",
"color",
"blur",
"size",
"misc"
]
}


5 changes: 5 additions & 0 deletions src/Augmentor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export

ElasticDistortion,

ColorJitter,
GaussianBlur,

augment,
augment!,
augmentbatch!,
Expand All @@ -95,6 +98,8 @@ include("operations/resize.jl")
include("operations/scale.jl")
include("operations/zoom.jl")
include("operations/either.jl")
include("operations/color.jl")
include("operations/blur.jl")

include("distortionfields.jl")
include("distortedview.jl")
Expand Down
76 changes: 76 additions & 0 deletions src/operations/blur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import ImageFiltering: imfilter, KernelFactors.gaussian

"""
GaussianBlur <: ImageOperation
Description
--------------
Blurs an image using a Gaussian filter.
Usage
--------------
GaussianBlur(k, [σ])
Arguments
--------------
- **`k`** : `Integer` or `AbstractVector` of `Integer` that denote
the kernel size. It must be an odd positive number.
- **`σ`** : Optional. `Real` or `AbstractVector` of `Real` that denote the
standard deviation. It must be a positive number.
Defaults to `0.3 * ((k - 1) / 2 - 1) + 0.8`.
Examples
--------------
```
using Augmentor
img = testpattern()
# use exactly k=3 and σ=1.0
augment(img, GaussianBlur(3, 1.0))
# pick k and σ randomly from the specified ranges
augment(img, GaussianBlur(3:2:7, 1.0:0.1:2.0))
```
"""
struct GaussianBlur{K <: AbstractVector, S <: AbstractVector} <: ImageOperation
k::K
σ::S

function GaussianBlur(k::K, σ::S) where {K <: AbstractVector{<:Integer},
S <: AbstractVector{<:Real}}
minimum(k) > 0 || throw(ArgumentError("Kernel size must be positive: $(k)"))
minimum(σ) > 0 || throw(ArgumentError("σ must be positive: $(σ)"))
new{K, S}(k, σ)
end
end

# The default value for σ is taken from Albumentations
GaussianBlur(k) = GaussianBlur(k, 0.3 * ((k - 1) / 2 - 1) + 0.8)
GaussianBlur(k, σ) = GaussianBlur(vectorize(k), vectorize(σ))

randparam(op::GaussianBlur, img) = (safe_rand(op.k), safe_rand(op.σ))

@inline supports_eager(::Type{<:GaussianBlur}) = true

function applyeager(op::GaussianBlur, img::AbstractArray, (k, σ))
n = ndims(img)
kernel = gaussian(ntuple(_->σ, n), ntuple(_->k, n))
return imfilter(img, kernel)
end

function showconstruction(io::IO, op::GaussianBlur)
print(io, typeof(op).name.name, '(', op.k, ", ", op.σ,')')
end

function Base.show(io::IO, op::GaussianBlur)
if get(io, :compact, false)
print(io, "GaussianBlur with k=$(op.k) and σ=$(op.σ)")
else
print(io, "Augmentor.")
showconstruction(io, op)
end
end
119 changes: 119 additions & 0 deletions src/operations/color.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import ImageCore: clamp01, gamutmax
import Statistics: mean

"""
ColorJitter <: ImageOperation
Description
--------------
Adjusts the brightness and contrast of an image according to the formula
`α * image[i] + β * M`, where `M` is either `mean(image)` or the maximum
intensity value.
Usage
--------------
ColorJitter()
ColorJitter(α, β; [usemax])
Arguments
--------------
- **`α`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s)
for contrast adjustment. Defaults to `0.8:0.1:1.2`.
- **`β`** : `Real` or `AbstractVector` of `Real` that denote the coefficient(s)
for brightness adjustment. Defaults to `-0.2:0.1:0.2`.
- **`usemax::Bool`**: Optional. If `true`, the brightness will be adjusted by
the maximum intensity value; otherwise, the image mean will be used.
Defaults to `true`.
Examples
--------------
```
using Augmentor
img = testpattern()
# use exactly 1.2 for contrast, and one of 0.5 and 0.8 for brightness
augment(img, ColorJitter(1.2, [0.5, 0.8]))
# pick the coefficients randomly from the specified ranges
augment(img, ColorJitter(0.8:0.1:2.0, 0.5:0.1:1.1))
```
"""
struct ColorJitter{A<:AbstractVector, B<:AbstractVector} <: ImageOperation
α::A
β::B
usemax::Bool

function ColorJitter::A, β::B, usemax) where {A<:AbstractVector{<:Real},
B<:AbstractVector{<:Real}}
length(α) > 0 || throw(ArgumentError("Range $(α) is empty"))
length(β) > 0 || throw(ArgumentError("Range $(β) is empty"))
new{A, B}(α, β, usemax)
end
end

# The default values for α and β are taken from Albumentations
ColorJitter() = ColorJitter(0.8:0.1:1.2, -0.2:0.1:0.2, true)
ColorJitter(α, β; usemax=true) = ColorJitter(vectorize(α), vectorize(β), usemax)

randparam(op::ColorJitter, img) = (safe_rand(op.α), safe_rand(op.β))

@inline supports_eager(::Type{<:ColorJitter}) = true
@inline supports_lazy(::Type{<:ColorJitter}) = true

function applyeager(op::ColorJitter, img::AbstractArray, (α, β))
M = _get_M(op, img)
return _map_pix.(α, β, M, img)
end

function applylazy(op::ColorJitter, img::AbstractArray, (α, β))
M = _get_M(op, img)
return ColorJitterView(img, α, β, M)
end

function showconstruction(io::IO, op::ColorJitter)
print(io, typeof(op).name.name, '(', op.α, ", ", op.β, ", ", op.usemax, ')')
end

function Base.show(io::IO, op::ColorJitter)
if get(io, :compact, false)
maxmsg = op.usemax ? "max. intensity" : "mean value";
print(io, "Color jitter with coffecients α=$(op.α) and β=$(op.β) (w.r.t. $(maxmsg))")
else
print(io, "Augmentor.")
showconstruction(io, op)
end
end

_map_pix(α, β, M, pix) = clamp01* pix + β * M)

function _get_M(op::ColorJitter, img)
T = eltype(img)
if op.usemax
return T(gamutmax(T)...)
else
return convert(T, mean(img))
end
end

# This wraps an image so that its pixels appear as after the contrast and
# brightness adjustment. This is done in the `getindex` method.
struct ColorJitterView{T, P<:AbstractMatrix{T}, R} <: AbstractArray{T, 2}
orig::P
α::R
β::R
M::T

function ColorJitterView(img::P, α::R, β::R, M) where {P <: AbstractMatrix,
R <: Real}
new{eltype(P), P, R}(img, α, β, M)
end
end

Base.parent(A::ColorJitterView) = A.parent
Base.size(A::ColorJitterView) = size(A.orig)
Base.axes(A::ColorJitterView) = axes(A.orig)
Base.@propagate_inbounds Base.getindex(A::ColorJitterView, i, j) = _map_pix(A.α, A.β, A.M, A.orig[i, j])
35 changes: 35 additions & 0 deletions test/operations/tst_blur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@testset "GaussianBlur" begin
@testset "constructor" begin
@test_throws ArgumentError GaussianBlur(0)
@test_throws ArgumentError GaussianBlur(3, 0)
@test_throws ArgumentError GaussianBlur([1, 3], 0)
@test_throws ArgumentError GaussianBlur([1, 0], 1)
@test_throws ArgumentError GaussianBlur(3, -1:1)
end
@testset "randparam" begin
img = testpattern()
ks = [3, 1:2:5, [1, 3, 5]]
σs = [1, 1:0.1:2, [1, 2]]
for k in ks, σ in σs
op = GaussianBlur(k, σ)
p = @inferred Augmentor.randparam(op, img)
@test p[1] in k
@test p[2] in σ
end
end
@testset "eager" begin
@test Augmentor.supports_eager(GaussianBlur)

k = 3
σ = 3.2

imgs = [testpattern(), camera]

for img in imgs
img = convert.(RGB, img)
ref = imfilter(img, KernelFactors.gaussian((σ, σ), (k, k)))
res = Augmentor.applyeager(GaussianBlur(k, σ), img)
@test assess_psnr(res, ref) > 25
end
end
end
Loading

0 comments on commit a121ac9

Please sign in to comment.