From f6626768fc0dd1053c506a73d982277683d54c1e Mon Sep 17 00:00:00 2001 From: "Longhao.Chen" Date: Wed, 4 Aug 2021 19:18:26 +0800 Subject: [PATCH] Add BM3D(sparse 3D transform-domain collaborative filtering) denoising algorithm (#21) --- Project.toml | 3 ++ docs/examples/reduce_noise/BM3D.jl | 32 +++++++++++++++++ src/ReduceNoise/BM3DDenoise.jl | 55 ++++++++++++++++++++++++++++++ src/ReduceNoise/ReduceNoise.jl | 3 ++ test/ReduceNoise/BM3DDenoise.jl | 14 ++++++++ test/runtests.jl | 1 + 6 files changed, 108 insertions(+) create mode 100644 docs/examples/reduce_noise/BM3D.jl create mode 100644 src/ReduceNoise/BM3DDenoise.jl create mode 100644 test/ReduceNoise/BM3DDenoise.jl diff --git a/Project.toml b/Project.toml index 7f99ef0..ae1d120 100644 --- a/Project.toml +++ b/Project.toml @@ -4,12 +4,15 @@ authors = ["Johnny Chen "] version = "0.1.2" [deps] +BM3DDenoise = "95fb3b36-088a-43fb-bb1b-b1f34fadbd7d" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] +BM3DDenoise = "1.0.1" Distributions = "0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25" ImageCore = "0.9" Reexport = "0.2, 1.0" diff --git a/docs/examples/reduce_noise/BM3D.jl b/docs/examples/reduce_noise/BM3D.jl new file mode 100644 index 0000000..667abcd --- /dev/null +++ b/docs/examples/reduce_noise/BM3D.jl @@ -0,0 +1,32 @@ +# --- +# title: The BM3D(sparse 3D transform-domain collaborative filtering) denoising algorithm. +# id: bm3d-demo +# cover: assets/bm3d_cover.png +# date: 2021-08-04 +# author: "[Longhao Chen](https://github.com/Longhao-Chen)" +# description: Use the BM3D denoising algorithm to reduce gaussian noise +# --- + +using ImageNoise +using TestImages, ImageShow, ImageCore, ImageQualityIndexes, ImageTransformations +using FileIO, Random #src + +# First, load an image and and add gaussian noise to it + +gray_img = float.(imresize(testimage("cameraman"), ratio=0.5)) +n = AdditiveWhiteGaussianNoise(0.1) +noisy_img = apply_noise(gray_img, n) + +# Then calling the standard `reduce_noise` API + +f_bm3d = BM3D(0.1) +denoised_img = reduce_noise(noisy_img, f_bm3d) + +mosaicview(gray_img, noisy_img, denoised_img; nrow=1) + +# Get the PSNR using ImageQualityIndexes package: + +assess_psnr(gray_img, denoised_img) + +mkpath("assets") #src +save("assets/bm3d_cover.png", denoised_img) #src diff --git a/src/ReduceNoise/BM3DDenoise.jl b/src/ReduceNoise/BM3DDenoise.jl new file mode 100644 index 0000000..36174b4 --- /dev/null +++ b/src/ReduceNoise/BM3DDenoise.jl @@ -0,0 +1,55 @@ +using UUIDs +const BM3DDenoise = Base.PkgId(UUID("95fb3b36-088a-43fb-bb1b-b1f34fadbd7d"), "BM3DDenoise") + +# Rewrite from ImageIO.jl +function checked_import(pkgid) + Base.root_module_exists(pkgid) && return Base.root_module(pkgid) + # If not available, load the library or throw an error. + Base.require(pkgid) +end + + +@doc raw""" + BM3D(σ [, config=bm3d_config()]) + +The BM3D(sparse 3D transform-domain collaborative filtering) denoising algorithm. + +# Arguments + +* `σ::Float64` is the variance of the noise. + +* `config::bm3d_config` is `BM3DDenoise.bm3d_config`. + +# Examples + +```julia +img = testimage("lena_color_256") + +n = AdditiveWhiteGaussianNoise(0.1) +noisy_img = apply_noise(img, n) + +# use default arguments +f_denoise = BM3D(0.1) +denoised_img = reduce_noise(noisy_img, f_denoise) +``` + +See also: [`reduce_noise`](@ref), [`reduce_noise!`](@ref) +""" +struct BM3D <: AbstractImageDenoiseAlgorithm + """degree of filtering""" + σ::Float64 + """bm3d_config""" + config + function BM3D(σ, config) + σ > 0 || @warn "σ is supposed to be positive" + new(σ, config) + end +end +BM3D(σ) = BM3D(σ, Base.invokelatest(checked_import(BM3DDenoise).bm3d_config)) + +function (f::BM3D)(out::AbstractArray{T}, + img::AbstractArray) where T + axes(out) == axes(img) || ArgumentError("Images should have the same axes.") + out .= T.(Base.invokelatest(checked_import(BM3DDenoise).bm3d, img, f.σ, f.config)) + return out +end \ No newline at end of file diff --git a/src/ReduceNoise/ReduceNoise.jl b/src/ReduceNoise/ReduceNoise.jl index 99b7a06..63deca3 100644 --- a/src/ReduceNoise/ReduceNoise.jl +++ b/src/ReduceNoise/ReduceNoise.jl @@ -9,11 +9,14 @@ using ImageCore: NumberLike, GenericGrayImage, GenericImage import ..NoiseAPI: AbstractImageDenoiseAlgorithm, reduce_noise, reduce_noise! include("compat.jl") +include("BM3DDenoise.jl") include("NonlocalMean.jl") export reduce_noise, reduce_noise!, + # BM3D + BM3D, # Non-local mean filter for gaussian noise NonlocalMean, get_NonlocalMean_rp diff --git a/test/ReduceNoise/BM3DDenoise.jl b/test/ReduceNoise/BM3DDenoise.jl new file mode 100644 index 0000000..cb28061 --- /dev/null +++ b/test/ReduceNoise/BM3DDenoise.jl @@ -0,0 +1,14 @@ +@testset "BM3DDenoise" begin + @info "Test: BM3DDenoise" + @testset "Numeric" begin + img_gray = n0f8.(imresize(testimage("lena_gray_256"); ratio=0.25)) + n = AdditiveWhiteGaussianNoise(0.05) + noisy_img = apply_noise(img_gray, n; rng=MersenneTwister(0)) + + f = BM3D(0.05) + denoised_img = reduce_noise(noisy_img, f) + # further modification shall not decrease psnr and ssim + @test assess(PSNR(), denoised_img, img_gray) >= 28. + @test assess(SSIM(), denoised_img, img_gray) >= 0.9 + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 075acf0..529a2aa 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,6 +12,7 @@ include("ApplyNoise/AdditiveWhiteGaussianNoise.jl") # ReduceNoise @info "Test: ReduceNoise" include("ReduceNoise/NonlocalMean.jl") +include("ReduceNoise/BM3DDenoise.jl") end nothing