Sparse + Low-Rank Splitting

Suppose we are given a data matrix $A$, and know that it has a form $$A=L+S,$$ where $L$ is of low-rank and $S$ is sparse, but we know neither the rank of $L$ nor the non-zero entries of $S$.

Can we recover $L$ and $S$?

The answer is YES, with high probability and with an efficient algorithm.

Sparse + Low-rank splitting can be successfully applied to video surveilance, face recognition, latent semantic indexing, and ranking and collaborative filtering.

Prerequisites

The reader should be familiar with linear algebra concepts, particularly SVD and its properties and algorithms.

Competences

The reader should be able to apply sparse + low-rank splitting to real problems.

References

For more details see E. J. Candes, X. Li, Y. Ma, and J. Wright, Robust Principal Component Analysis?

Credits: The author wishes to thank Dimitar Ninevski, a former IAESTE intern, for collecting and preparing some of the material.

Definitions

Let $A\in\mathbb{R}^{m\times n}$ have rank $r$, and let $A=U\Sigma V^T$ be its SVD.

The nuclear norm of $A$ is $\|A\|_*=\sum\limits_{i=1}^r \sigma_i(A)$.

Let $\|A\|_1=\sum\limits_{i,j} |A_{ij}|$ denote the $1$-norm of $A$ seen as a long vector.

Let $\|A\|_{\infty}=\max\limits_{i,j} |A_{ij}|$ denote the $\infty$-norm of $A$ seen as a long vector.

Given $\tau>0$, the shrinkage operator $\mathcal{S}_{\displaystyle\tau}:\mathbb{R}\to \mathbb{R}$ is defined by $\mathcal{S}_{\displaystyle\tau}(x)=\mathop{\mathrm{sign}}(x)\max\{|x|-\tau,0\}$, and is extended to matrices by applying it to each element.

Given $\tau>0$, the singluar value thresholding operator is $\mathcal{D}_{\displaystyle\tau}(A)=U \mathcal{S}_{\displaystyle\tau} (\Sigma) V^T$.

Example


In [1]:
# Shrinkage
function Shr(x::Array{T},τ::T) where T
    sign.(x).*max.(abs.(x).-τ,zero(T))
end


Out[1]:
Shr (generic function with 1 method)

In [15]:
import Random
Random.seed!(421)
A=2*rand(3,5).-1


Out[15]:
3×5 Array{Float64,2}:
 -0.309113   0.946105  -0.65984    -0.728924   0.217224
  0.36974   -0.789731   0.0504169   0.91673   -0.306879
  0.301982   0.54494    0.571694    0.120972   0.122497

In [16]:
Shr(A,0.5)


Out[16]:
3×5 Array{Float64,2}:
 -0.0   0.446105   -0.15984    -0.228924   0.0
  0.0  -0.289731    0.0         0.41673   -0.0
  0.0   0.0449404   0.0716939   0.0        0.0

In [17]:
using LinearAlgebra

In [18]:
# Singular value thresholding
function D(A::Array{T},τ::T) where T
    # U,σ,V=svd(A)
    # This can be replaced by a faster approach
    V=svd(A)
    S=Shr(V.S,τ)
    k=count(!iszero,S)
    return (V.U[:,1:k]*Diagonal(S[1:k]))*V.Vt[1:k,:]
end


Out[18]:
D (generic function with 1 method)

In [19]:
A


Out[19]:
3×5 Array{Float64,2}:
 -0.309113   0.946105  -0.65984    -0.728924   0.217224
  0.36974   -0.789731   0.0504169   0.91673   -0.306879
  0.301982   0.54494    0.571694    0.120972   0.122497

In [20]:
svdvals(A)


Out[20]:
3-element Array{Float64,1}:
 1.869323076650321 
 0.8882982512088067
 0.4040399346152457

In [21]:
C=D(A,0.5)


Out[21]:
3×5 Array{Float64,2}:
 -0.278691   0.633626  -0.334118  -0.632073     0.189125 
  0.205417  -0.655484   0.189336   0.558462    -0.193806 
  0.106145   0.232356   0.270365   0.00804585   0.0647208

In [22]:
svdvals(C)


Out[22]:
3-element Array{Float64,1}:
 1.3693230766503202   
 0.3882982512088064   
 5.732695393069556e-17

Facts

Let $A=L+S$ be the splitting that we are looking for.

  1. The problem can be formulated as $\mathop{\textrm{arg min}}\limits_{\displaystyle \mathop{\mathrm{rank}}(L)\leq k} \|A-L\|_2$.

  2. The problem makes sense if the incoherence conditions $$ \max_{\displaystyle i} \| U_{:,1:r}^T e_i\|_2^2\leq \frac{\mu r}{m}, \quad \max_{\displaystyle i} \| V_{:,1:r}^T e_i\|_2^2\leq \frac{\mu r}{n}, \quad \|UV^T\|_{\infty} \leq \sqrt{\frac{\mu r}{m\cdot n}}, $$ hold for some parameter $\mu$.

  3. If the incoherence conditions are satisfied, the Principal Component Pursuit estimate, $$ \mathop{\textrm{arg min}}\limits_{\displaystyle L+S=A} \|L\|_*+\lambda \|S\|_2, $$ exactly recovers $L$ and $S$.

  4. Principal Component Pursuit by Alternating Directions algorithm finds the above estimate

    1. Initialization: $S=0$, $Y=0$, $L=0$, $\mu>0$, $\delta=10^{-7}$.
    2. Iterations: while $\|A-L-S\|_F>\delta\|A\|_F$ repeat
      1. SV Thresholding: $L=\mathcal{D}_{\displaystyle \mu^{-1}}(A-S+\mu^{-1}Y)$
      2. Shrinkage: $S=\mathcal{S}_{\displaystyle \lambda \mu^{-1}}(A-L+\mu^{-1}Y)$
      3. Updating: $Y=Y+\mu(A-L-S)$

In [23]:
function PCPAD(A::Array{T}) where T
    # Initialize
    δ=1.0e-7
    tol=δ*norm(A)
    m,n=size(A)
    S=zero(A)
    Y=zero(A)
    L=zero(A)
    T₁=zero(A)
    μ=(m*n)/(4*(norm(A[:],1)))
    μ₁=one(T)/μ
    λ=one(T)/sqrt(max(m,n))
    λμ₁=λ*μ₁
    ν=1e20
    maxiter=200
    iterations=0
    # Iterate
    while (ν>tol) && iterations<maxiter
        # println(iterations," ",ν)
        iterations+=1
        L=D(A-S+μ₁*Y,μ₁)
        S=Shr(A-L+μ₁.*Y,λμ₁)
        T₁=(A-L-S)
        Y+=(μ.*T₁)
        ν=norm(T₁)
    end
    L,S, iterations
end


Out[23]:
PCPAD (generic function with 1 method)

In [24]:
@time L,S,iter=PCPAD(A)


  0.160223 seconds (104.33 k allocations: 5.212 MiB, 5.31% gc time)
Out[24]:
([-0.309113 0.477914 … -0.54588 0.217223; 0.36974 -0.636708 … 0.668523 -0.306879; 0.0906774 0.0233471 … 0.120972 0.0545497], [0.0 0.468191 … -0.183044 0.0; 0.0 -0.153022 … 0.248208 -0.0; 0.211305 0.521593 … 0.0 0.0679469], 146)

In [25]:
rank(L),norm(A-L-S)


Out[25]:
(2, 1.9559838307622215e-7)

In [26]:
L


Out[26]:
3×5 Array{Float64,2}:
 -0.309113    0.477914   -0.0686682  -0.54588    0.217223 
  0.36974    -0.636708    0.050417    0.668523  -0.306879 
  0.0906774   0.0233471   0.0998753   0.120972   0.0545497

In [27]:
S


Out[27]:
3×5 Array{Float64,2}:
 0.0        0.468191  -0.591172  -0.183044   0.0      
 0.0       -0.153022  -0.0        0.248208  -0.0      
 0.211305   0.521593   0.471819   0.0        0.0679469

In [28]:
# Now the real test
# Dimensions of the matrix
m=100
n=100
# Rank of the low-rank part L
k=10
# Generate L
L=rand(m,k)*rand(k,n)
rank(L)


Out[28]:
10

In [30]:
using SparseArrays

In [31]:
# Sparsity of the sparse part S
sparsity=0.1
# Generate S
S=10*sprand(m,n,sparsity)
nnz(S)


Out[31]:
1006

In [32]:
# Generate the matrix A, it is a full matrix with full rank
A=L+S
rank(A)


Out[32]:
100

In [33]:
# Decompose A into L₁ and S₁
@time L₁,S₁,iters=PCPAD(A);


  0.680531 seconds (5.57 k allocations: 186.996 MiB, 5.49% gc time)

In [34]:
iters, rank(L₁), norm(L), norm(L-L₁), norm(S), norm(S-S₁)


Out[34]:
(132, 10, 253.97328079239296, 3.7800817205128095e-5, 181.41757653755946, 2.230745684264843e-5)

Although there might be no convergence, the splitting is still good.


In [35]:
S₁


Out[35]:
100×100 Array{Float64,2}:
  0.0   0.0          0.0      …   2.62652    0.0       -0.0       
 -0.0   0.0         -0.0          1.83969    0.0       -0.0       
 -0.0   0.0          2.13844     -0.0       -0.0        4.42683e-7
  0.0   0.0         -0.0          0.877967   0.0        0.0       
  0.0  -0.0         -0.0          0.0        7.20853    0.0       
 -0.0   0.0          0.0      …   1.01969   -0.0       -0.0       
 -0.0   5.94334e-7   8.59015     -0.0        0.0        0.0       
 -0.0   4.45529      0.0          0.0       -0.0        0.0       
  0.0   0.0          0.0         -0.0        0.0       -0.0       
 -0.0   0.0         -0.0          0.0        0.0        0.0       
  0.0   0.0          0.0      …  -0.0       -0.0       -0.0       
 -0.0   0.0          0.0         -0.0        0.0        7.9206    
  0.0  -0.0         -0.0          0.0       -0.0       -0.0       
  ⋮                           ⋱                                   
  0.0  -9.95267e-7   0.0          0.0        0.0        5.40395   
  0.0  -0.0         -0.0          5.28074   -0.0        2.73518   
  0.0  -0.0          0.0      …  -0.0        0.0        9.77218   
  0.0   0.0          0.0         -0.0       -0.0       -0.0       
  0.0   0.0          0.0          0.0        0.0        0.0       
  0.0  -0.0         -0.0         -0.0       -0.0        7.68705   
 -0.0  -0.0         -0.0          0.0        0.0        0.0       
  0.0   0.0          0.0      …   0.0        0.0       -0.0       
 -0.0   0.0          0.0          0.0        0.202439  -0.0       
  0.0  -0.0          0.0         -0.0        0.0        0.0       
 -0.0   0.0          0.0         -0.0       -0.0       -0.0       
  0.0  -0.0          0.0          0.0       -0.0       -0.0       

In [36]:
Matrix(S)


Out[36]:
100×100 Array{Float64,2}:
 0.0  0.0      0.0      0.0      0.0      0.0  …  2.62652   0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     1.83969   0.0       0.0    
 0.0  0.0      2.13844  0.0      0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.877966  0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       7.20853   0.0    
 0.0  0.0      0.0      0.0      0.0      0.0  …  1.01969   0.0       0.0    
 0.0  0.0      8.59015  0.0      0.0      0.0     0.0       0.0       0.0    
 0.0  4.45529  0.0      0.0      0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      6.83133  0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0  …  0.0       0.0       0.0    
 0.0  0.0      0.0      7.54778  1.36709  0.0     0.0       0.0       7.9206 
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.0       0.0    
 ⋮                                        ⋮    ⋱                             
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.0       5.40395
 0.0  0.0      0.0      0.0      3.27699  0.0     5.28074   0.0       2.73518
 0.0  0.0      0.0      0.0      0.0      0.0  …  0.0       0.0       9.77218
 0.0  0.0      0.0      0.0      1.25463  0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      4.83018  0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.0       7.68705
 0.0  0.0      0.0      0.0      1.59596  0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0  …  0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.202438  0.0    
 0.0  0.0      0.0      0.0      0.0      0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      5.8469   0.0     0.0       0.0       0.0    
 0.0  0.0      0.0      0.0      3.69859  0.0     0.0       0.0       0.0    

Example - Face recognition

We will try to recover missing features. The images are chosen from the Yale Face Database.


In [37]:
using Images

In [39]:
# First a single image
img=load("files/17.jpg")


Out[39]:

In [40]:
show(img[1,1])


Gray{N0f8}(0.325)

In [41]:
A=map(Float64,img)


Out[41]:
192×168 Array{Float64,2}:
 0.32549     0.305882    0.290196   …  0.0470588  0.0431373  0.0392157
 0.301961    0.294118    0.290196      0.0470588  0.0431373  0.0392157
 0.286275    0.294118    0.309804      0.0470588  0.0431373  0.0392157
 0.290196    0.313725    0.341176      0.0470588  0.0431373  0.0431373
 0.301961    0.32549     0.352941      0.054902   0.0470588  0.0431373
 0.309804    0.32549     0.341176   …  0.0627451  0.0509804  0.0470588
 0.321569    0.32549     0.32549       0.0745098  0.0588235  0.0470588
 0.337255    0.329412    0.321569      0.0784314  0.0588235  0.0470588
 0.329412    0.32549     0.313725      0.0980392  0.0705882  0.0509804
 0.32549     0.329412    0.333333      0.109804   0.0862745  0.0705882
 0.317647    0.337255    0.352941   …  0.117647   0.101961   0.0941176
 0.32549     0.352941    0.376471      0.117647   0.105882   0.0980392
 0.352941    0.380392    0.4           0.113725   0.101961   0.0901961
 ⋮                                  ⋱  ⋮                              
 0.027451    0.027451    0.027451   …  0.027451   0.027451   0.0313725
 0.0313725   0.0313725   0.0313725     0.027451   0.027451   0.0313725
 0.0313725   0.0313725   0.0313725     0.027451   0.027451   0.0313725
 0.0313725   0.0313725   0.0313725     0.027451   0.027451   0.0313725
 0.0196078   0.027451    0.0352941     0.0352941  0.0352941  0.0352941
 0.0196078   0.027451    0.0352941  …  0.0352941  0.0352941  0.0352941
 0.0156863   0.0235294   0.0313725     0.0352941  0.0352941  0.0352941
 0.0117647   0.0196078   0.027451      0.0352941  0.0352941  0.0352941
 0.00392157  0.0156863   0.0235294     0.0352941  0.0352941  0.0352941
 0.0         0.00784314  0.0196078     0.0352941  0.0352941  0.0352941
 0.0         0.00392157  0.0156863  …  0.0352941  0.0352941  0.0352941
 0.0         0.00392157  0.0156863     0.0352941  0.0352941  0.0352941

In [42]:
# Compute the splitting and show number of iterations
@time L,S,iters=PCPAD(A)
iters, rank(L), norm(A), norm(A-L-S)


  4.150486 seconds (9.02 k allocations: 926.632 MiB, 3.47% gc time)
Out[42]:
(200, 78, 40.54262512632593, 0.004239753054553026)

In [43]:
colorview(Gray,L)


Out[43]:

In [45]:
# Try S+0.5
colorview(Gray,S.+0.5)


Out[45]:

In [47]:
hcat(colorview(Gray,0.9*L+S.+0.2),img)


Out[47]:

In [48]:
# Another image
img=load("files/19.jpg")


Out[48]:

In [50]:
A=map(Float64,img)
L,S,iters=PCPAD(A)
iters, rank(L), norm(A), norm(A-L-S)


Out[50]:
(200, 79, 53.08353496111299, 0.006883332844262259)

In [51]:
hcat(img,colorview(Gray,L),colorview(Gray,L+S.+0.3))


Out[51]:

Example - Multiple images

Each image of $168 \times 192$ pixels is converted to a vector of length 32256. All vectors are stacked in the columns of matrix $A$, and the low-rank + sparse splitting of $A$ is computed.


In [52]:
# Load all images in the collection
dir="./files/yaleB08/"
files=readdir(dir)


Out[52]:
64-element Array{String,1}:
 ".directory"              
 "yaleB08_P00A+000E+00.pgm"
 "yaleB08_P00A+000E+20.pgm"
 "yaleB08_P00A+000E+45.pgm"
 "yaleB08_P00A+000E-20.pgm"
 "yaleB08_P00A+000E-35.pgm"
 "yaleB08_P00A+005E+10.pgm"
 "yaleB08_P00A+005E-10.pgm"
 "yaleB08_P00A+010E+00.pgm"
 "yaleB08_P00A+010E-20.pgm"
 "yaleB08_P00A+015E+20.pgm"
 "yaleB08_P00A+020E+10.pgm"
 "yaleB08_P00A+020E-10.pgm"
 ⋮                         
 "yaleB08_P00A-070E+00.pgm"
 "yaleB08_P00A-070E+45.pgm"
 "yaleB08_P00A-070E-35.pgm"
 "yaleB08_P00A-085E+20.pgm"
 "yaleB08_P00A-085E-20.pgm"
 "yaleB08_P00A-095E+00.pgm"
 "yaleB08_P00A-110E+15.pgm"
 "yaleB08_P00A-110E+40.pgm"
 "yaleB08_P00A-110E+65.pgm"
 "yaleB08_P00A-110E-20.pgm"
 "yaleB08_P00A-120E+00.pgm"
 "yaleB08_P00A-130E+20.pgm"

In [53]:
# import Pkg; Pkg.add("Netpbm")

In [54]:
n=length(files)-1
images=Array{Any}(undef,n)
B=Array{Any}(undef,n)
for i=1:n
    images[i]=Images.load(joinpath(dir,files[i+1]))
    B[i]=map(Float64,images[i])
end

In [56]:
# See the images - last 9 images are meaningless
# for i=1:n; display(images[i]); end

In [57]:
# Form the big matrix - each image is converted to a column vector
mi,ni=size(images[1])
A=Array{Float64}(undef,mi*ni,n-9)
for i=1:n-9
    A[:,i]=vec(B[i])
end
size(A)


Out[57]:
(32256, 54)

In [58]:
# Now the big SVDs - 1 minute
@time L,S,iters=PCPAD(A)
iters, rank(L), norm(A), norm(A-L-S)


 83.977956 seconds (8.84 k allocations: 39.289 GiB, 31.49% gc time)
Out[58]:
(200, 27, 419.0677046909673, 0.005202750277097147)

In [60]:
reshape(S[:,1],mi,ni)


Out[60]:
192×168 Array{Float64,2}:
 -0.00495669    0.0           0.0          …   0.0038532     0.000100633
 -0.00674239    0.0          -0.00115572       0.00252207    0.00518379 
 -0.00419446    0.0          -0.00442436       0.0018296     0.00586078 
  0.00120403   -0.0025382    -0.00135281      -0.00239806    0.003269   
 -0.0          -0.0           0.000585718     -0.00671884   -0.00840207 
  0.00363616    9.67909e-5   -0.00240804   …   0.0150508     0.00486174 
  0.000295163  -0.00246132    0.0048211        0.00467998    0.00391104 
 -0.00299876   -0.000282387   0.00251491       0.00134999    0.00466737 
  0.00317675    0.00294359    0.00415725       0.0100172     0.00455386 
  0.0059954    -0.000949929   0.0              0.0123673     0.0108048  
 -0.00121714   -0.00352895    0.000452436  …   0.00236832    0.00195632 
 -0.00300623   -0.00516701    0.00204093      -0.00736337   -0.000781504
  0.00572153    0.0123604     0.00470371       0.000977364  -0.00835812 
  ⋮                                        ⋱                            
 -0.00732547   -0.00848667   -0.00593046   …  -0.0145267    -0.0224824  
 -0.0129012    -0.0120205    -0.0113034       -0.0280766    -0.0475079  
 -0.0171073    -0.0173951    -0.0101049       -0.0384434    -0.0728879  
 -0.0272396    -0.0195715    -0.0136935       -0.0576088    -0.0801672  
 -0.0468059    -0.0298748    -0.0164091       -0.0738866    -0.0840981  
 -0.0620241    -0.0516819    -0.034051     …  -0.084868     -0.0815665  
 -0.0597468    -0.0733005    -0.0561467       -0.07815      -0.062875   
 -0.0352161    -0.0606596    -0.0765237       -0.0663102    -0.0437423  
 -0.00633156   -0.0291362    -0.0606954       -0.0412407    -0.0211474  
  0.0          -0.0105194    -0.0288584       -0.018892     -0.0104468  
  0.00865091    0.0          -0.00905945   …   0.0           0.0        
  0.0159087    -0.0          -0.0              0.0           0.0        

In [61]:
for i=1:10 # n-9
    Li=reshape(L[:,i],mi,ni)
    Si=reshape(S[:,i],mi,ni).+0.3
    display(hcat(images[i],colorview(Gray,Li),colorview(Gray,Li+Si))) 
end


Example - Mona Lisa's smile


In [62]:
img=load("files/mona-lisa_1.jpg")


Out[62]:

In [66]:
# As in the notebook S03
imgsep=map(Float64,channelview(img))


Out[66]:
3×456×620 Array{Float64,3}:
[:, :, 1] =
 0.396078  0.505882  0.603922  0.603922  …  0.203922   0.290196  0.258824
 0.419608  0.529412  0.627451  0.615686     0.227451   0.313725  0.286275
 0.239216  0.34902   0.447059  0.439216     0.0862745  0.164706  0.12549 

[:, :, 2] =
 0.411765  0.482353  0.54902   0.545098  …  0.247059  0.329412  0.309804
 0.435294  0.505882  0.572549  0.556863     0.270588  0.356863  0.337255
 0.254902  0.32549   0.392157  0.380392     0.129412  0.196078  0.172549

[:, :, 3] =
 0.498039  0.509804  0.517647  0.490196  …  0.282353  0.337255  0.32549 
 0.521569  0.533333  0.529412  0.501961     0.305882  0.364706  0.352941
 0.341176  0.352941  0.352941  0.32549      0.156863  0.2       0.188235

...

[:, :, 618] =
 0.811765  0.764706  0.788235  0.870588  …  0.239216  0.294118  0.333333
 0.764706  0.717647  0.741176  0.823529     0.219608  0.266667  0.305882
 0.568627  0.521569  0.545098  0.627451     0.105882  0.156863  0.196078

[:, :, 619] =
 0.745098  0.807843  0.882353  0.905882  …  0.235294  0.278431  0.372549
 0.698039  0.760784  0.835294  0.858824     0.215686  0.25098   0.345098
 0.501961  0.564706  0.639216  0.662745     0.101961  0.141176  0.235294

[:, :, 620] =
 0.733333  0.85098   0.929412  0.87451   …  0.270588  0.32549   0.470588
 0.686275  0.803922  0.882353  0.827451     0.25098   0.298039  0.443137
 0.490196  0.607843  0.686275  0.631373     0.137255  0.188235  0.333333

In [67]:
size(imgsep)


Out[67]:
(3, 456, 620)

In [68]:
# 1-2 minutes
# Red
@time RL,RS,Riter=PCPAD(imgsep[1,:,:])
# Green
GL,GS,Giter=PCPAD(imgsep[2,:,:])
# Blue
BL,BS,Biter=PCPAD(imgsep[1,:,:])


 42.966298 seconds (92.89 k allocations: 7.717 GiB, 2.80% gc time)
Out[68]:
([0.468059 0.498645 … 0.776629 0.779654; 0.505883 0.500784 … 0.807843 0.831849; … ; 0.236331 0.271822 … 0.278431 0.306364; 0.219413 0.255513 … 0.290064 0.315771], [-0.0719802 -0.0868803 … -0.031531 -0.0463207; 0.0 -0.0184311 … -0.0 0.0191313; … ; 0.0538651 0.0575895 … -0.0 0.0191265; 0.0394101 0.054291 … 0.0824855 0.154818], 200)

In [69]:
Giter, rank(GL), norm(imgsep[2,:,:]), norm(imgsep[2,:,:]-GL-GS)


Out[69]:
(200, 246, 242.35427739335933, 0.0012380151503696812)

In [70]:
# Mona Lisa's low-rank component
colorview(RGB,RL,GL,BL)


Out[70]:

In [71]:
# Mona Lisa's sparse component
colorview(RGB,RS.+0.5,GS.+0.5,BS.+0.5)


Out[71]:

Example - Mona Lisa's hands


In [50]:
img=load("files/Mona_Lisa_detail_hands.jpg")


Out[50]:

In [51]:
imgsep=float(channelview(img))
@time RL,RS,Riter=PCPAD(imgsep[1,:,:])
GL,GS,Giter=PCPAD(imgsep[2,:,:])
BL,BS,Biter=PCPAD(imgsep[1,:,:])
Giter, rank(GL), norm(imgsep[2,:,:]), 
norm(imgsep[2,:,:]-GL-GS)


 50.110518 seconds (8.42 k allocations: 5.402 GiB, 1.65% gc time)
Out[51]:
(200, 306, 215.53857f0, 0.00048123093f0)

In [52]:
colorview(RGB,RL,GL,BL)


Out[52]:

In [53]:
colorview(RGB,RS.+0.5,GS.+0.5,BS.+0.5)


Out[53]:

In [ ]: