Spaces:
Running
Running
Commit ·
962923b
0
Parent(s):
first commit
Browse files- .gitattributes +2 -0
- .gitignore +52 -0
- LICENSE.txt +11 -0
- README.md +154 -0
- magic_eye_solver.py +111 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
examples/*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
examples/*.gif filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# IDE #
|
| 3 |
+
#######
|
| 4 |
+
*.wpr
|
| 5 |
+
*.wpu
|
| 6 |
+
|
| 7 |
+
# Directories #
|
| 8 |
+
###############
|
| 9 |
+
build/
|
| 10 |
+
dist/
|
| 11 |
+
*.egg-info
|
| 12 |
+
|
| 13 |
+
# Compiled source #
|
| 14 |
+
###################
|
| 15 |
+
*.com
|
| 16 |
+
*.class
|
| 17 |
+
*.dll
|
| 18 |
+
*.exe
|
| 19 |
+
*.o
|
| 20 |
+
*.so
|
| 21 |
+
*.pyc
|
| 22 |
+
|
| 23 |
+
# Packages #
|
| 24 |
+
############
|
| 25 |
+
# it's better to unpack these files and commit the raw source
|
| 26 |
+
# git has its own built in compression methods
|
| 27 |
+
*.7z
|
| 28 |
+
*.dmg
|
| 29 |
+
*.gz
|
| 30 |
+
*.iso
|
| 31 |
+
*.jar
|
| 32 |
+
*.rar
|
| 33 |
+
*.tar
|
| 34 |
+
*.zip
|
| 35 |
+
*.egg
|
| 36 |
+
|
| 37 |
+
# Logs and databases #
|
| 38 |
+
######################
|
| 39 |
+
*.log
|
| 40 |
+
*.sql
|
| 41 |
+
*.sqlite
|
| 42 |
+
|
| 43 |
+
# OS generated files #
|
| 44 |
+
######################
|
| 45 |
+
.DS_Store
|
| 46 |
+
.DS_Store?
|
| 47 |
+
._*
|
| 48 |
+
.Spotlight-V100
|
| 49 |
+
.Trashes
|
| 50 |
+
Icon?
|
| 51 |
+
ehthumbs.db
|
| 52 |
+
Thumbs.db
|
LICENSE.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 2 |
+
you may not use this file except in compliance with the License.
|
| 3 |
+
You may obtain a copy of the License at
|
| 4 |
+
|
| 5 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 6 |
+
|
| 7 |
+
Unless required by applicable law or agreed to in writing, software
|
| 8 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 9 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 10 |
+
See the License for the specific language governing permissions and
|
| 11 |
+
limitations under the License.
|
README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+

|
| 2 |
+
|
| 3 |
+
# magiceye-solver
|
| 4 |
+
Are you as frustrated by not being able to see those magic eye optical illusions as I am? Well, now you're in luck.
|
| 5 |
+
|
| 6 |
+
This is a short python code that demonstrates how to automatically "solve" a magic eye autostereogram by estimating a
|
| 7 |
+
projection of the underlying image. This provides a decent contour outline of the hidden object, though most finer detail
|
| 8 |
+
tends to be lost.
|
| 9 |
+
|
| 10 |
+
Requirements:
|
| 11 |
+
---------------
|
| 12 |
+
|
| 13 |
+
- Python 2.7+
|
| 14 |
+
- Numpy 1.5+
|
| 15 |
+
- Scipy 0.12+
|
| 16 |
+
|
| 17 |
+
Optional:
|
| 18 |
+
|
| 19 |
+
- scikit-image 0.8+ (code will attempt to import filtering functions for additional post processing, but will not raise an error if
|
| 20 |
+
library is not available)
|
| 21 |
+
|
| 22 |
+
Example usages:
|
| 23 |
+
----------
|
| 24 |
+
This code can be used in three different ways.
|
| 25 |
+
### Run directly, with a command line argument:
|
| 26 |
+
|
| 27 |
+
Run `magic_eye_solver.py` with the filename of the image that you would like to
|
| 28 |
+
process passed as an argument:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
$ python magic_eye_solver.py "example_images/example1.png"
|
| 32 |
+
```
|
| 33 |
+
This generates text output:
|
| 34 |
+
```
|
| 35 |
+
Solving image example_images/example1.png...
|
| 36 |
+
Saving solution to example_images/example1_solution.png...
|
| 37 |
+
Saving joined to example_images/example1_joined.png...
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
This will generate `example1_solution.png` and `example1_joined.png` in the same
|
| 41 |
+
directory as the original files, showing the computed solution and a side-by-side
|
| 42 |
+
comparison of the original image and the solution, respectively.
|
| 43 |
+
|
| 44 |
+
### Run directly, without a command line argument:
|
| 45 |
+
If a filename is not specified, a list of png and jpg image files will be presented
|
| 46 |
+
to the user, who will then be prompted to select from the file names shown.
|
| 47 |
+
|
| 48 |
+
For example, we run the file directly, with no argument:
|
| 49 |
+
```bash
|
| 50 |
+
$ python magic_eye_solver.py
|
| 51 |
+
```
|
| 52 |
+
Select all example files (example1.png through example7.png) from a generated list (notice that banner.png is not
|
| 53 |
+
selected by the user):
|
| 54 |
+
```
|
| 55 |
+
Please select from the following images:
|
| 56 |
+
========================================
|
| 57 |
+
(selection filename)
|
| 58 |
+
0 example_images/banner.png
|
| 59 |
+
1 example_images/example1.png
|
| 60 |
+
2 example_images/example2.png
|
| 61 |
+
3 example_images/example3.png
|
| 62 |
+
4 example_images/example4.png
|
| 63 |
+
5 example_images/example5.png
|
| 64 |
+
6 example_images/example6.png
|
| 65 |
+
7 example_images/example7.png
|
| 66 |
+
|
| 67 |
+
Make selections (separate by commas): 1,2,3,4,5,6,7
|
| 68 |
+
|
| 69 |
+
Solving image example_images/example1.png...
|
| 70 |
+
Saving solution to example_images/example1_solution.png...
|
| 71 |
+
Saving joined to example_images/example1_joined.png...
|
| 72 |
+
|
| 73 |
+
Solving image example_images/example2.png...
|
| 74 |
+
Saving solution to example_images/example2_solution.png...
|
| 75 |
+
Saving joined to example_images/example2_joined.png...
|
| 76 |
+
|
| 77 |
+
Solving image example_images/example3.png...
|
| 78 |
+
Saving solution to example_images/example3_solution.png...
|
| 79 |
+
Saving joined to example_images/example3_joined.png...
|
| 80 |
+
|
| 81 |
+
Solving image example_images/example4.png...
|
| 82 |
+
Saving solution to example_images/example4_solution.png...
|
| 83 |
+
Saving joined to example_images/example4_joined.png...
|
| 84 |
+
|
| 85 |
+
Solving image example_images/example5.png...
|
| 86 |
+
Saving solution to example_images/example5_solution.png...
|
| 87 |
+
Saving joined to example_images/example5_joined.png...
|
| 88 |
+
|
| 89 |
+
Solving image example_images/example6.png...
|
| 90 |
+
Saving solution to example_images/example6_solution.png...
|
| 91 |
+
Saving joined to example_images/example6_joined.png...
|
| 92 |
+
|
| 93 |
+
Solving image example_images/example7.png...
|
| 94 |
+
Saving solution to example_images/example7_solution.png...
|
| 95 |
+
Saving joined to example_images/example7_joined.png...
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Imported and used as a library:
|
| 99 |
+
The `solve_magiceye()` method can also be imported from `magic_eye_solver.py` and
|
| 100 |
+
used in your own application like any other image/array processing function:
|
| 101 |
+
|
| 102 |
+
```python
|
| 103 |
+
from magic_eye_solver import solve_magiceye
|
| 104 |
+
import pylab #matplotlib plotting
|
| 105 |
+
|
| 106 |
+
image = pylab.imread("example_images/example1.png") #load magiceye image
|
| 107 |
+
|
| 108 |
+
solution = solve_magiceye(image) #solve it
|
| 109 |
+
|
| 110 |
+
pylab.imshow(solution, cmap = pylab.cm.gray) #plot the solution
|
| 111 |
+
|
| 112 |
+
pylab.show() #show the plot
|
| 113 |
+
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
How it works:
|
| 117 |
+
-------------
|
| 118 |
+
- For each of the R, G, and B channels of a magic eye image:
|
| 119 |
+
1. An autocorrelation is computed (via FFT) to find strong horizontal periodicities in the inputted image
|
| 120 |
+
2. The sum of all horizontal translative shifts of the image up to the peak autocorrelation
|
| 121 |
+
shift is computed. That is, the entire image is "smeared" horizontally by the distance determined in step 1.
|
| 122 |
+
3. An edge detection and uniform filter is applied to clean up the resulting sum and help separate the cumulated noise
|
| 123 |
+
from useful objective information.
|
| 124 |
+
- The most leptokurtotic (high in sample kurtosis) of three channels processed as above is returned as the solution
|
| 125 |
+
that is most likely to have the clearest 2D grayscale projection of the underlying image.
|
| 126 |
+
|
| 127 |
+
Notes / todo list:
|
| 128 |
+
---------
|
| 129 |
+
- The post-process filtering should be improved to clean up the output a bit more. The solutions are kind of grainy.
|
| 130 |
+
- This certainly seems to work better for some autostereogram images than others, but still seems to give generally
|
| 131 |
+
useful output for the test images I've been able to collect so far.
|
| 132 |
+
- Good alternative solution methods are likely to exist, so there is still plenty of
|
| 133 |
+
experimentation left to do with this.
|
| 134 |
+
- I experimented with PCA and ICA (both as pre-processing the R, G, B channels and as post-processing of the results),
|
| 135 |
+
but this didn't improve the results very much.
|
| 136 |
+
|
| 137 |
+
Example results
|
| 138 |
+
----------------
|
| 139 |
+
The following examples show the computed solutions of the magiceye images
|
| 140 |
+
found in the `example_images` directory.
|
| 141 |
+
|
| 142 |
+

|
| 143 |
+
|
| 144 |
+

|
| 145 |
+
|
| 146 |
+

|
| 147 |
+
|
| 148 |
+

|
| 149 |
+
|
| 150 |
+

|
| 151 |
+
|
| 152 |
+

|
| 153 |
+
|
| 154 |
+

|
magic_eye_solver.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from numpy.fft import fft2, ifft2
|
| 3 |
+
from scipy.ndimage import filters
|
| 4 |
+
from scipy.stats import kurtosis
|
| 5 |
+
from scipy.misc import imread, imsave
|
| 6 |
+
try:
|
| 7 |
+
from skimage import filter as ski_filter
|
| 8 |
+
except:
|
| 9 |
+
ski_filter = None
|
| 10 |
+
|
| 11 |
+
def fft_2d_autocorr(x):
|
| 12 |
+
""" 2D autocorrelation, using FFT"""
|
| 13 |
+
fr = fft2(x)
|
| 14 |
+
fr2 = fft2(np.flipud(np.fliplr(x)))
|
| 15 |
+
m,n = fr.shape
|
| 16 |
+
cc = np.real(ifft2(fr*fr2))
|
| 17 |
+
cc = np.roll(cc, -m/2+1,axis=0)
|
| 18 |
+
cc = np.roll(cc, -n/2+1,axis=1)
|
| 19 |
+
return cc
|
| 20 |
+
|
| 21 |
+
def offset(mx):
|
| 22 |
+
ac = fft_2d_autocorr(mx)
|
| 23 |
+
ac=ac[len(ac)/2]
|
| 24 |
+
idx= np.where((ac-np.median(ac))/ac.std() > 3)[0]
|
| 25 |
+
diffs=[]
|
| 26 |
+
diffs = np.ediff1d(idx)
|
| 27 |
+
return np.max( diffs)
|
| 28 |
+
|
| 29 |
+
def shift_pic(mx):
|
| 30 |
+
gap = offset(mx)
|
| 31 |
+
m,n = mx.shape
|
| 32 |
+
mx2 = np.zeros((m,n))
|
| 33 |
+
for i in xrange(int(gap)):
|
| 34 |
+
mx2 += np.roll(mx,-i, axis = 1)
|
| 35 |
+
return mx2[:,:-gap]
|
| 36 |
+
|
| 37 |
+
def post_process(mx2):
|
| 38 |
+
mx2 = ski_filter.hprewitt(mx2)
|
| 39 |
+
return mx2
|
| 40 |
+
|
| 41 |
+
def solve_magiceye(x):
|
| 42 |
+
m,n,c = x.shape
|
| 43 |
+
k_ = -3
|
| 44 |
+
for i in xrange(3):
|
| 45 |
+
color = x[:,:,i]
|
| 46 |
+
mx2 = shift_pic(color)
|
| 47 |
+
mx2 = filters.prewitt(mx2)
|
| 48 |
+
mx2 = filters.uniform_filter(mx2, size = (5,5))
|
| 49 |
+
if ski_filter:
|
| 50 |
+
mx2 = post_process(mx2)
|
| 51 |
+
k = kurtosis(mx2.flatten())
|
| 52 |
+
if k > k_:
|
| 53 |
+
solution = mx2
|
| 54 |
+
k_ = k
|
| 55 |
+
return solution
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
"""
|
| 59 |
+
Generates solutions either from from command line arguments
|
| 60 |
+
or by selection of suitable images generated from a list
|
| 61 |
+
"""
|
| 62 |
+
import os
|
| 63 |
+
import sys
|
| 64 |
+
if len(sys.argv) < 2:
|
| 65 |
+
pngs = []
|
| 66 |
+
for root, dirs, files in os.walk(os.getcwd()):
|
| 67 |
+
dirname = root.split(os.path.sep)[-1]
|
| 68 |
+
for fn in files:
|
| 69 |
+
typ = fn.split('.')[-1]
|
| 70 |
+
if typ == 'png' or typ == "jpg":
|
| 71 |
+
if dirname != os.getcwd().split('/')[-1]:
|
| 72 |
+
pngs.append('/'.join([dirname,fn]))
|
| 73 |
+
else:
|
| 74 |
+
pngs.append(fn)
|
| 75 |
+
print
|
| 76 |
+
print "Please select from the following images:"
|
| 77 |
+
print "========================================"
|
| 78 |
+
print "(selection filename)"
|
| 79 |
+
i = 0
|
| 80 |
+
for fn in pngs:
|
| 81 |
+
print i, fn
|
| 82 |
+
i+=1
|
| 83 |
+
print
|
| 84 |
+
print "Make selections (separate by commas):",
|
| 85 |
+
select = raw_input()
|
| 86 |
+
fns = [pngs[int(sub.strip())] for sub in select.split(',')]
|
| 87 |
+
else:
|
| 88 |
+
fns = [sys.argv[1]]
|
| 89 |
+
print
|
| 90 |
+
for fn in fns:
|
| 91 |
+
x=imread(fn)
|
| 92 |
+
m,n,_ = x.shape
|
| 93 |
+
print "Solving image %s..." % fn
|
| 94 |
+
solution = solve_magiceye(x)
|
| 95 |
+
sfn = fn.split('.')[0]+'_solution.png'
|
| 96 |
+
print "Saving solution to %s..." % sfn
|
| 97 |
+
imsave(sfn,solution)
|
| 98 |
+
|
| 99 |
+
solution=imread(sfn)
|
| 100 |
+
n2=solution.shape[1]
|
| 101 |
+
joined = np.zeros((m,n+n2,3))
|
| 102 |
+
joined[:,:n,:] = x
|
| 103 |
+
joined[:,n:,0] = solution
|
| 104 |
+
joined[:,n:,1] = solution
|
| 105 |
+
joined[:,n:,2] = solution
|
| 106 |
+
|
| 107 |
+
sfn = fn.split('.')[0]+'_joined.png'
|
| 108 |
+
print "Saving joined to %s..." % sfn
|
| 109 |
+
imsave(sfn,joined)
|
| 110 |
+
print
|
| 111 |
+
|