-
Notifications
You must be signed in to change notification settings - Fork 2
implement generic ROF model using Chambolle04 primal-dual method #24
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
module Models | ||
|
||
using ImageCore | ||
using ImageCore.MappedArrays: of_eltype | ||
using ..FiniteDiff | ||
|
||
# Introduced in ColorVectorSpace v0.9.3 | ||
# https://github.com/JuliaGraphics/ColorVectorSpace.jl/pull/172 | ||
using ImageCore.ColorVectorSpace.Future: abs2 | ||
|
||
""" | ||
This submodule provides predefined image-related models and its solvers that can be reused | ||
by many image processing tasks. | ||
|
||
- solve the Rudin Osher Fatemi (ROF) model using the primal-dual method: [`solve_ROF_PD`](@ref) | ||
""" | ||
Models | ||
|
||
export solve_ROF_PD | ||
|
||
|
||
##### implementation details | ||
|
||
""" | ||
solve_ROF_PD(img::AbstractArray, λ; kwargs...) | ||
|
||
Perform Rudin-Osher-Fatemi (ROF) filtering, more commonly known as Total Variation (TV) | ||
denoising or TV regularization. | ||
|
||
This function applies to generic n-dimensional colorant array. | ||
|
||
# Arguments | ||
|
||
- `img`: the input image, usually is a noisy image. | ||
- `λ`: the regularization coefficient. Larger `λ` would produce more smooth image. | ||
|
||
# Parameters | ||
|
||
- `num_iters::Int`: The number of iterations before stopping. | ||
|
||
# Examples | ||
|
||
```julia | ||
using ImageBase | ||
using ImageBase.Models: solve_ROF_PD | ||
using ImageQualityIndexes | ||
using TestImages | ||
|
||
img_ori = float.(testimage("cameraman")) | ||
img_noisy = img_ori .+ 0.1 .* randn(size(img_ori)) | ||
assess_psnr(img_noisy, img_ori) # ~20 dB | ||
|
||
img_smoothed = solve_ROF_PD(img_noisy, 0.015, 50) | ||
assess_psnr(img_smoothed, img_ori) # ~27 dB | ||
|
||
# larger λ produces over-smoothed result | ||
img_smoothed = solve_ROF_PD(img_noisy, 5, 50) | ||
assess_psnr(img_smoothed, img_ori) # ~21 dB | ||
``` | ||
|
||
# Extended help | ||
|
||
Mathematically, this function solves the following ROF model using the primal-dual method: | ||
|
||
```math | ||
\\min_u \\lVert u - g \\rVert^2 + \\lambda\\lvert\\nabla u\\rvert | ||
``` | ||
|
||
# References | ||
|
||
- [1] Chambolle, A. (2004). "An algorithm for total variation minimization and applications". _Journal of Mathematical Imaging and Vision_. 20: 89–97 | ||
- [2] https://en.wikipedia.org/wiki/Total_variation_denoising | ||
""" | ||
function solve_ROF_PD(img::AbstractArray, λ::Real, num_iters::Integer) | ||
# Total Variation regularized image denoising using the primal dual algorithm | ||
# Implement according to reference [1] | ||
τ = 1/4 # see 2nd remark after proof of Theorem 3.1. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid forcing things to |
||
|
||
g = of_eltype(floattype(eltype(img)), img) # use the same symbol in the paper | ||
u = similar(g) | ||
p = fgradient(g) | ||
div_p = similar(g) | ||
∇u = map(similar, p) | ||
∇u_mag = similar(g, eltype(eltype(g))) | ||
|
||
# This iterates Eq. (9) of [1] | ||
# TODO(johnnychen94): set better stop criterion | ||
for _ in 1:num_iters | ||
fdiv!(div_p, p) | ||
# multiply term inside ∇ by -λ. Thm. 3.1 relates this to `u` via Eq. 7. | ||
@. u = g - λ*div_p | ||
fgradient!(∇u, u) | ||
_l2norm_vec!(∇u_mag, ∇u) # |∇(g - λdiv p)| | ||
# Eq. (9): update p | ||
for i in 1:length(p) | ||
@. p[i] = (p[i] - (τ/λ)*∇u[i])/(1 + (τ/λ) * ∇u_mag) | ||
end | ||
end | ||
return u | ||
end | ||
|
||
|
||
function _l2norm_vec!(out, Vs::Tuple) | ||
all(v->axes(out) == axes(v), Vs) || throw(ArgumentError("All axes of input data should be the same.")) | ||
@. out = abs2(Vs[1]) | ||
for v in Vs[2:end] | ||
@. out += abs2(v) | ||
end | ||
@. out = sqrt(out) | ||
return out | ||
end | ||
|
||
|
||
end # module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using ImageBase.Models | ||
|
||
@testset "solve_ROF_PD" begin | ||
# Note: random seed really matters a lot | ||
|
||
@testset "Numerical" begin | ||
# 2D Gray | ||
img = restrict(testimage("cameraman")) | ||
img_noisy = img .+ 0.05randn(MersenneTwister(0), size(img)) | ||
img_smoothed = solve_ROF_PD(img_noisy, 0.05, 20) | ||
@test ndims(img_smoothed) == 2 | ||
@test eltype(img_smoothed) <: Gray | ||
@test assess_psnr(img_smoothed, img) > 31.67 | ||
@test assess_ssim(img_smoothed, img) > 0.90 | ||
|
||
# 2D RGB | ||
img = restrict(testimage("lighthouse")) | ||
img_noisy = img .+ colorview(RGB, ntuple(i->0.05.*randn(MersenneTwister(i), size(img)), 3)...) | ||
img_smoothed = solve_ROF_PD(img_noisy, 0.03, 20) | ||
@test ndims(img_smoothed) == 2 | ||
@test eltype(img_smoothed) <: RGB | ||
@test assess_psnr(img_smoothed, img) > 32.15 | ||
@test assess_ssim(img_smoothed, img) > 0.90 | ||
|
||
# 3D Gray | ||
img = restrict(testimage("mri"), (1, 2)) | ||
img_noisy = img .+ 0.05randn(MersenneTwister(0), size(img)) | ||
img_smoothed = solve_ROF_PD(img_noisy, 0.02, 20) | ||
@test ndims(img_smoothed) == 3 | ||
@test eltype(img_smoothed) <: Gray | ||
@test assess_psnr(img_smoothed, img) > 31.78 | ||
@test assess_ssim(img_smoothed, img) > 0.85 | ||
|
||
# 3D RGB | ||
img = RGB.(restrict(testimage("mri"), (1, 2))) | ||
img_noisy = img .+ colorview(RGB, ntuple(i->0.05.*randn(MersenneTwister(i), size(img)), 3)...) | ||
img_smoothed = solve_ROF_PD(img_noisy, 0.02, 20) | ||
@test ndims(img_smoothed) == 3 | ||
@test eltype(img_smoothed) <: RGB | ||
@test assess_psnr(img_smoothed, img) > 31.17 | ||
@test assess_ssim(img_smoothed, img) > 0.79 | ||
end | ||
|
||
@testset "FixedPointNumbers" begin | ||
A = rand(N0f8, 20, 20) | ||
@test solve_ROF_PD(A, 0.01, 5) ≈ solve_ROF_PD(float32.(A), 0.01, 5) | ||
end | ||
|
||
@testset "OffsetArray" begin | ||
Ao = OffsetArray(rand(N0f8, 20, 20), -1, -1) | ||
out = solve_ROF_PD(Ao, 0.01, 5) | ||
@test axes(out) == axes(Ao) | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have my fingers crossed for JuliaLang/julia#41915