Last active
April 9, 2023 05:21
-
-
Save 3dsf/efc323b7766171fd2c0331423b8907b9 to your computer and use it in GitHub Desktop.
A python stereogram generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
adjusted
remove_backfrom default of .3 to 0 in argparse section