Skip to content

Commit ad3815c

Browse files
authored
update svd demo (#117)
* change cover image to gif * add explaination to SVD compression
1 parent 43880fc commit ad3815c

File tree

1 file changed

+32
-16
lines changed

1 file changed

+32
-16
lines changed

docs/examples/color_channels/color_separations_svd.jl

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,50 @@
11
# ---
2-
# cover: assets/color_separations_svd.png
3-
# title: Color separations and the SVD
2+
# cover: assets/color_separations_svd.gif
3+
# title: Image Compression using SVD
44
# ---
55

66
# This demonstration shows how to work with color channels to explore image compression
77
# using the Singular Value Decomposition (SVD).
88

9-
using Images, TestImages, LinearAlgebra
9+
using Images, TestImages, MosaicViews
10+
using LinearAlgebra
1011

11-
img = testimage("mandrill")
12-
channels = channelview(float.(img))
12+
img = float.(testimage("mandrill"))
13+
channels = channelview(img)
1314

1415
function rank_approx(F::SVD, k)
1516
U, S, V = F
1617
M = U[:, 1:k] * Diagonal(S[1:k]) * V[:, 1:k]'
17-
M = min.(max.(M, 0.0), 1.)
18+
clamp01!(M)
1819
end
19-
#md nothing #hide
20+
nothing #hide #md
2021

21-
#-
22-
## after julia v1.1: svd.(eachslice(channels; dims=1))
22+
# For each channel, we do SVD decomposition, and then reconstruct the channel using only the K
23+
# largest singular values.
24+
25+
# The image is compressed because for each channel we only need to save two small matrices and one
26+
# vector -- truncated part of `(U, S, V)`. For example, if the original image is gray image of size
27+
# `(512, 512)`, and we rebuild the image with $50$ singular values, then we only need to save
28+
# $2 \times 512 \times 50 + 50$ numbers to rebuild the image, while original image has
29+
# $512 \times 512$ numbers. Hence this gives us a compression ratio $19.55\%$ if we don't consider
30+
# the storage type.
31+
32+
## after julia v1.1:
33+
## svdfactors = svd.(eachslice(channels; dims=1))
2334
svdfactors = (svd(channels[1,:,:]), svd(channels[2,:,:]), svd(channels[3,:,:]))
2435
imgs = map((10, 50, 100)) do k
25-
colorview(RGB,
26-
rank_approx(svdfactors[1], k),
27-
rank_approx(svdfactors[2], k),
28-
rank_approx(svdfactors[3], k))
36+
colorview(RGB, rank_approx.(svdfactors, k)...)
2937
end
3038

31-
vcat([img imgs[1]], [imgs[2] imgs[3]])
39+
mosaicview(img, imgs...; nrow=1, npad=10)
40+
41+
# From left to right: original image, reconstructed images using 10, 50, 100 largest singular values.
42+
# We can see that $50$ largest singular values are capable of rebuilding a pretty good image.
43+
44+
# --- save covers --- #src
45+
using ImageMagick #src
46+
imgs = map(10:5:50) do k #src
47+
colorview(RGB, rank_approx.(svdfactors, k)...) #src
48+
end #src
49+
ImageMagick.save("assets/color_separations_svd.gif", cat(imgs...; dims=3); fps=2) #src
3250

33-
cover = hcat(imgs[1]) #src
34-
save("assets/color_separations_svd.png", cover) #src

0 commit comments

Comments
 (0)