Skip to content

Instantly share code, notes, and snippets.

@3dsf
Last active April 9, 2023 05:21
Show Gist options
  • Select an option

  • Save 3dsf/efc323b7766171fd2c0331423b8907b9 to your computer and use it in GitHub Desktop.

Select an option

Save 3dsf/efc323b7766171fd2c0331423b8907b9 to your computer and use it in GitHub Desktop.
A python stereogram generator
#!/usr/bin/env python3
'''
u/3dsf
d88P Y88b
888 888
.d8888b 888 888d888 8888b. 88888b.d88b.
88K 888 88888 888P" "88b 888 "888 "88b
"Y8888b. 888 888 888 .d888888 888 888 888
X88 Y88b d88P 888 888 888 888 888 888
88888P' "Y8888P88 888 "Y888888 888 888 888
'''
# Python stereogram generator
# Not super fast, but fast enough
# Early parallelisation efforts were not successful, and may not make sense for
# smaller single images
# Required libries are numpy and opencv (cv2), installing opencv will do both
'''
python -m pip install opencv-python
'''
import numpy as np
from os import path
import argparse
import cv2
# depthmap(np.array) and pattern(np.array) are required inputs
def make_autostereogram(depthmap, pattern,
kickoff=0, maxPixelStretch=28,
kickoffOverEncoder=0, removeBack=0.3,
patternShift=0, fast=False,
squishFront=0, squishBack=0,
blur=0,
):
# Creates an autostereogram from depthmap and pattern
# Defines funtions used by make autostereogram function
# -----------------------------------------------------
# Normalizes depthmap between 0 and 1
def normalize(depthmap):
if depthmap.max() != depthmap.min():
return (depthmap - depthmap.min()) / (depthmap.max() - depthmap.min())
else:
return depthmap
# Control wether to use round() or int() in main autostereogram function
# Using int() is blockier but faster (Will vary by cpu, but 100% on mine)
def roundORint(stuff):
if fast:
return int(stuff)
else:
return round(stuff)
# Init stuff
# ----------
# Creates blank stereogram canvas
autostereogram = np.zeros_like(depthmap, dtype=pattern.dtype)
# Creates primary pattern (Basically doubles it in x directions)
dPattern = np.concatenate([pattern,pattern], axis=1)
# Depth Map Operations
# --------------------
# Greyscaling
depthmap = np.dot(depthmap[...,:3], [0.2989, 0.5870, 0.1140]) #depthmap = cv2.cvtColor(depthmap, cv2.COLOR_BGR2GRAY)
# Normaliztion
depthmap = normalize(depthmap)
# Program creators prefernce -- to reverse depth map change polarity of maxPixelStretch -mpa
depthmap = 1 - depthmap
# Remove back depth space by 0-1
depthmap = np.fmax(depthmap,np.full(depthmap.shape,depthmap.min()+(depthmap.max()-depthmap.min())*removeBack))
# Squish front or back
if squishFront + squishBack > 0:
for i in range(0,squishFront):
depthmap = np.where(depthmap>(1-(i*.05)),depthmap*.95, depthmap)
for i in range(0,squishBack):
depthmap = np.where(depthmap<((i*.05)),depthmap*.95, depthmap)
depthmap = normalize(depthmap)
# Depthmap blur in x direction
if blur > 0:
depthmap = cv2.GaussianBlur(depthmap,(1,blur),0)
# Pattern Operations
# ------------------
# See var kmps below
# Stereogram Generation Magic
# ---------------------------
# Sets vairables to make looping quicker
pY = int(pattern.shape[0])
pX = int(pattern.shape[1])
kmpX = int(kickoff - pX)
kppX = int(kickoff + pX)
kmps = int(kickoff-patternShift) # patternShift
hpX = int(pX/2)
hpXmmps = int(hpX - maxPixelStretch)
kpmpspkoe = int(kickoff + maxPixelStretch + kickoffOverEncoder)
dd = depthmap * maxPixelStretch - pX
aX = int(autostereogram.shape[1])
aY = int(autostereogram.shape[0])
for y in range(aY):
for x in range(kmpX, aX):
# lays pattern from kickoff right
if x < kppX:
autostereogram[y, x] = dPattern[ roundORint( y % pY ), x - kmps ]
else:
autostereogram[y, x] = autostereogram[
y,
roundORint( x + dd[ y, x - hpXmmps ] )
]
for x in reversed(range(kpmpspkoe)):
# lays pattern from kickoff left
autostereogram[y, x] = autostereogram[
y,
roundORint( x - dd[ y, x + hpX ] )
]
return autostereogram, depthmap
if __name__=='__main__':
parser = argparse.ArgumentParser(description='sGram.py -- a python stereogram generator',
epilog="ex// python sGram.py -d depthmap.jpg -p pattern.png -o myStereoGram.png -max 25 -k 480",)
required = parser.add_argument_group('Required', '')
required.add_argument('-d', '--depthmap', type=str, required=True, metavar='<file>',
help='Path to depthmap')
required.add_argument('-p', '--pattern', type=str, required=True, metavar='<file>',
help='Path to pattern; I think pattern scales to height... wierd')
generator = parser.add_argument_group('Generator Settings', '')
generator.add_argument('-o', '--outfile', type=str, default="", metavar='',
help='Output Path')
generator.add_argument('-s', '--show', action='store_true',
help='Display image')
generator.add_argument('-f', '--fast', action='store_true',
help='Use int() instead of round() -- Speedup but blockier hidden image')
generator.add_argument('-k', '--kickoff', type=int, default=0, metavar='',
help='starting point from which to render right; image width - pattern width is max')
generator.add_argument('-koe', '--kickoff_over_encoder', type=int, default=0, metavar='',
help='how many pixels to re-encode when switching encoding direction')
generator.add_argument('-max', '--max_pixel_stretch', type=int, default=28, metavar='',
help='3d multiplier by setting max pixels to move for highest point -- Reverse polarity to switch stereogram viewing style')
dmAdjust = parser.add_argument_group('Depthmap Adjustment', 'Does not change input depthmap file')
dmAdjust.add_argument('-rb', '--dm_remove_back', type=float, default=0, metavar='',
help='reduce background depth space [0-1]')
dmAdjust.add_argument('-sf', '--dm_squish_front', type=int, default=0, metavar='',
help='reduce background depth space [1-20]')
dmAdjust.add_argument('-sb', '--dm_squish_back', type=int, default=0, metavar='',
help='reduce background depth space [1-20]')
patAdjust = parser.add_argument_group('Depthmap Adjustment', 'Does not change input pattern file')
patAdjust.add_argument('-ps', '--pat_shift', type=int, default=0, metavar='',
help='Shift the pattern in the x direction between 0 and pattern width')
dmAdjust.add_argument('-b', '--dm_blur', type=int, default=0, metavar='[0-5]',
help='Gaussian blur by integer used')
args = parser.parse_args()
autostereogram, depthmap = make_autostereogram(
cv2.imread(args.depthmap),
cv2.imread(args.pattern),
maxPixelStretch= args.max_pixel_stretch,
kickoff= args.kickoff,
kickoffOverEncoder= args.kickoff_over_encoder,
removeBack= args.dm_remove_back,
patternShift= args.pat_shift,
fast= args.fast,
squishFront= args.dm_squish_front,
squishBack= args.dm_squish_back,
blur= args.dm_blur,
)
print()
print(args)
print()
# Outputs stereogram result if declared
if args.outfile != "":
cv2.imwrite(args.outfile, autostereogram)
if args.show:
toShow = autostereogram.copy()
title = 'press any key -- do not close window with X button'
if True:
#Draws marker on shown image (not in file output)
cv2.line(toShow, (args.kickoff,
autostereogram.shape[0]-50),
(args.kickoff, autostereogram.shape[0]),
color=(0,0,200), thickness=4)
print()
cv2.imshow(title, toShow)
k = cv2.waitKey(0)
print()
if True:
cv2.imshow(title + ' -- final depthmap',depthmap)
k = cv2.waitKey(0)
@3dsf
Copy link
Author

3dsf commented Apr 9, 2023

adjusted remove_back from default of .3 to 0 in argparse section

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment