comparison data_generation/transformations/pycaptcha/Captcha/Visual/Distortions.py @ 167:1f5937e9e530

More moves - transformations into data_generation, added "deep" folder
author Dumitru Erhan <dumitru.erhan@gmail.com>
date Fri, 26 Feb 2010 14:15:38 -0500
parents pycaptcha/Captcha/Visual/Distortions.py@4775b4195b4b
children
comparison
equal deleted inserted replaced
166:17ae5a1a4dd1 167:1f5937e9e530
1 """ Captcha.Visual.Distortions
2
3 Distortion layers for visual CAPTCHAs
4 """
5 #
6 # PyCAPTCHA Package
7 # Copyright (C) 2004 Micah Dowty <micah@navi.cx>
8 #
9
10 from Captcha.Visual import Layer
11 import ImageDraw, Image
12 import random, math
13
14
15 class WigglyBlocks(Layer):
16 """Randomly select and shift blocks of the image"""
17 def __init__(self, blockSize=3, sigma=0.01, iterations=300):
18 self.blockSize = blockSize
19 self.sigma = sigma
20 self.iterations = iterations
21 self.seed = random.random()
22
23 def render(self, image):
24 r = random.Random(self.seed)
25 for i in xrange(self.iterations):
26 # Select a block
27 bx = int(r.uniform(0, image.size[0]-self.blockSize))
28 by = int(r.uniform(0, image.size[1]-self.blockSize))
29 block = image.crop((bx, by, bx+self.blockSize-1, by+self.blockSize-1))
30
31 # Figure out how much to move it.
32 # The call to floor() is important so we always round toward
33 # 0 rather than to -inf. Just int() would bias the block motion.
34 mx = int(math.floor(r.normalvariate(0, self.sigma)))
35 my = int(math.floor(r.normalvariate(0, self.sigma)))
36
37 # Now actually move the block
38 image.paste(block, (bx+mx, by+my))
39
40
41 class WarpBase(Layer):
42 """Abstract base class for image warping. Subclasses define a
43 function that maps points in the output image to points in the input image.
44 This warping engine runs a grid of points through this transform and uses
45 PIL's mesh transform to warp the image.
46 """
47 filtering = Image.BILINEAR
48 resolution = 10
49
50 def getTransform(self, image):
51 """Return a transformation function, subclasses should override this"""
52 return lambda x, y: (x, y)
53
54 def render(self, image):
55 r = self.resolution
56 xPoints = image.size[0] / r + 2
57 yPoints = image.size[1] / r + 2
58 f = self.getTransform(image)
59
60 # Create a list of arrays with transformed points
61 xRows = []
62 yRows = []
63 for j in xrange(yPoints):
64 xRow = []
65 yRow = []
66 for i in xrange(xPoints):
67 x, y = f(i*r, j*r)
68
69 # Clamp the edges so we don't get black undefined areas
70 x = max(0, min(image.size[0]-1, x))
71 y = max(0, min(image.size[1]-1, y))
72
73 xRow.append(x)
74 yRow.append(y)
75 xRows.append(xRow)
76 yRows.append(yRow)
77
78 # Create the mesh list, with a transformation for
79 # each square between points on the grid
80 mesh = []
81 for j in xrange(yPoints-1):
82 for i in xrange(xPoints-1):
83 mesh.append((
84 # Destination rectangle
85 (i*r, j*r,
86 (i+1)*r, (j+1)*r),
87 # Source quadrilateral
88 (xRows[j ][i ], yRows[j ][i ],
89 xRows[j+1][i ], yRows[j+1][i ],
90 xRows[j+1][i+1], yRows[j+1][i+1],
91 xRows[j ][i+1], yRows[j ][i+1]),
92 ))
93
94 return image.transform(image.size, Image.MESH, mesh, self.filtering)
95
96
97 class SineWarp(WarpBase):
98 """Warp the image using a random composition of sine waves"""
99
100 def __init__(self,
101 amplitudeRange = (3, 6.5),
102 periodRange = (0.04, 0.1),
103 ):
104 self.amplitude = random.uniform(*amplitudeRange)
105 self.period = random.uniform(*periodRange)
106 self.offset = (random.uniform(0, math.pi * 2 / self.period),
107 random.uniform(0, math.pi * 2 / self.period))
108
109 def getTransform(self, image):
110 return (lambda x, y,
111 a = self.amplitude,
112 p = self.period,
113 o = self.offset:
114 (math.sin( (y+o[0])*p )*a + x,
115 math.sin( (x+o[1])*p )*a + y))
116
117 ### The End ###