Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GaussianBlur and ColorJitter ops. #84

Merged
merged 14 commits into from
Jul 12, 2021
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)
barucden marked this conversation as resolved.
Show resolved Hide resolved
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)
barucden marked this conversation as resolved.
Show resolved Hide resolved
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