1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2017, 2018 Oracle and/or its affiliates.
4
+ # Copyright (c) 2013 Pablo Mouzo
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ from array import array
25
+ from math import sqrt , atan2 , sqrt , sin , cos , ceil , floor
26
+
27
+
28
+ class Image (object ):
29
+ def __init__ (self , width , height , data = None ):
30
+ self .width = width
31
+ self .height = height
32
+ if data :
33
+ self .data = data
34
+ else :
35
+ self .data = [0 ] * (width * height )
36
+
37
+ def _idx (self , x , y ):
38
+ if 0 <= x < self .width and 0 <= y < self .height :
39
+ return y * self .width + x
40
+ raise IndexError
41
+
42
+ def __getitem__ (self , t ):
43
+ x , y = t
44
+ return self .data [self ._idx (x , y )]
45
+
46
+ def __setitem__ (self , t , val ):
47
+ x , y = t
48
+ self .data [self ._idx (x , y )] = val
49
+
50
+ def pixels (self , border = 0 ):
51
+ for y in xrange (border , self .height - border ):
52
+ for x in xrange (border , self .width - border ):
53
+ yield x , y
54
+
55
+ def sobel (self , horizontal = True , vertical = True ):
56
+ out = Image (self .width , self .height )
57
+ for y in range (1 , self .height - 1 ):
58
+ for x in range (1 , self .width - 1 ):
59
+ if horizontal :
60
+ dx = - 1.0 * self [x - 1 , y - 1 ] + 1.0 * self [x + 1 , y - 1 ] + \
61
+ - 2.0 * self [x - 1 , y ] + 2.0 * self [x + 1 , y ] + \
62
+ - 1.0 * self [x - 1 , y + 1 ] + 1.0 * self [x + 1 , y + 1 ]
63
+ else :
64
+ dx = self [x , y ]
65
+ if vertical :
66
+ dy = - 1.0 * self [x - 1 , y - 1 ] - 2.0 * self [x , y - 1 ] - 1.0 * self [x + 1 , y - 1 ] + \
67
+ 1.0 * self [x - 1 , y + 1 ] + 2.0 * self [x , y + 1 ] + 1.0 * self [x + 1 , y + 1 ]
68
+ else :
69
+ dy = self [x , y ]
70
+ out [x , y ] = min (int (sqrt (dx * dx + dy * dy ) / 4.0 ), 255 )
71
+ return out
72
+
73
+ def fisheye (img , fraction = 2 , bilinear = False ):
74
+ if bilinear :
75
+ img = BilinImage (img .width , img .height , data = img .data )
76
+ else :
77
+ img = NNImage (img .width , img .height , data = img .data )
78
+ out = Image (img .width , img .height , data = img .data [:])
79
+ maxr = img .height / (fraction + 1 )
80
+ for y in range (int (img .height / 2 - maxr ), int (img .height / 2 + maxr )):
81
+ for x in range (int (img .width / 2 - maxr ), int (img .width / 2 + maxr )):
82
+ dx , dy = x - img .width / 2 , y - img .height / 2
83
+ a = atan2 (dy , dx )
84
+ r = sqrt (dx ** 2 + dy ** 2 )
85
+ if r < maxr :
86
+ nr = r * r / maxr
87
+ nx , ny = nr * cos (a ), nr * sin (a )
88
+ out [x ,y ] = min (int (img [nx + img .width / 2 , ny + img .height / 2 ]), 255 )
89
+ else :
90
+ out [x ,y ] = img [x ,y ]
91
+ return out
92
+
93
+
94
+ class NNImage (Image ):
95
+ def __getitem__ (self , t ):
96
+ x , y = t
97
+ return Image .__getitem__ (self , (int (x + 0.5 ), int (y + 0.5 )))
98
+
99
+
100
+ class BilinImage (Image ):
101
+ def __getitem__ (self , t ):
102
+ x , y = t
103
+ if isinstance (x , float ) and isinstance (y , float ):
104
+ x0 , x1 = int (floor (x )), int (ceil (x ))
105
+ y0 , y1 = int (floor (y )), int (ceil (y ))
106
+ xoff , yoff = x - x0 , y - y0
107
+ return (1.0 - xoff ) * (1.0 - yoff ) * self [x0 , y0 ] + \
108
+ (1.0 - xoff ) * ( yoff ) * self [x0 , y1 ] + \
109
+ ( xoff ) * (1.0 - yoff ) * self [x1 , y0 ] + \
110
+ ( xoff ) * ( yoff ) * self [x1 , y1 ]
111
+ else :
112
+ return Image .__getitem__ (self , (x , y ))
113
+
114
+
115
+ if __name__ == '__main__' :
116
+ import sys
117
+ if sys .implementation .name == "graalpython" or "test" in sys .argv :
118
+ img = Image (5 , 5 , data = (
119
+ [11 , 12 , 13 , 14 , 15 ] +
120
+ [21 , 22 , 23 , 24 , 25 ] +
121
+ [31 , 32 , 33 , 34 , 35 ] +
122
+ [41 , 42 , 43 , 44 , 45 ] +
123
+ [51 , 52 , 53 , 54 , 55 ]
124
+ ))
125
+ print (img .sobel ().data )
126
+ print (img .fisheye ().data )
127
+ print (img .fisheye (bilinear = True ).data )
128
+ else :
129
+ import re , subprocess
130
+ from time import time
131
+
132
+ def mplayer (Image , fn = 'tv://' , options = '' ):
133
+ f = subprocess .Popen (
134
+ 'mplayer -really-quiet -noframedrop ' + options + ' ' '-vo yuv4mpeg:file=/dev/stdout 2>/dev/null </dev/null ' + fn ,
135
+ universal_newlines = False ,
136
+ shell = True ,
137
+ stdout = subprocess .PIPE ,
138
+ stderr = subprocess .PIPE ,
139
+ ).stdout
140
+ hdr = f .readline ()
141
+ m = re .search ('W(\d+) H(\d+)' , str (hdr ))
142
+ w , h = int (m .group (1 )), int (m .group (2 ))
143
+ while True :
144
+ hdr = f .readline ()
145
+ if hdr != b'FRAME\n ' :
146
+ break
147
+ data = array ('B' )
148
+ data .fromfile (f , w * h )
149
+ yield Image (w , h , data = list (data ))
150
+ f .read (w * h // 2 ) # Color data
151
+
152
+ class MplayerViewer (object ):
153
+ def __init__ (self ):
154
+ self .width = self .height = None
155
+
156
+ def view (self , img ):
157
+ from plain import Image
158
+ imgdata = array ('B' , [0 ]) * (img .width * img .height )
159
+ for idx , px in enumerate (img .data ):
160
+ imgdata [idx ] = px
161
+ if not self .width :
162
+ self .mplayer = subprocess .Popen (
163
+ 'mplayer -really-quiet -noframedrop - 2> /dev/null ' ,
164
+ stdin = subprocess .PIPE ,
165
+ stdout = subprocess .DEVNULL ,
166
+ universal_newlines = False ,
167
+ shell = True ,
168
+ ).stdin
169
+ self .mplayer .write (b'YUV4MPEG2 W%d H%d F100:1 Ip A1:1\n ' %
170
+ (img .width , img .height ))
171
+ self .width = img .width
172
+ self .height = img .height
173
+ self .color_data = array ('B' , [127 ]) * (img .width * img .height // 2 )
174
+ assert self .width == img .width
175
+ assert self .height == img .height
176
+ self .mplayer .write (b'FRAME\n ' )
177
+ imgdata .tofile (self .mplayer )
178
+ self .color_data .tofile (self .mplayer )
179
+
180
+ default_viewer = MplayerViewer ()
181
+
182
+ def view (img ):
183
+ default_viewer .view (img )
184
+
185
+ start = start0 = time ()
186
+ for fcnt , img in enumerate (mplayer (Image , 'test.avi -vf scale=640:480 -benchmark' )):
187
+ img = img .sobel (horizontal = ('vertical' not in sys .argv ), vertical = ('horizontal' not in sys .argv ))
188
+ if 'no-fisheye' not in sys .argv :
189
+ img = img .fisheye (bilinear = ("bilinear" in sys .argv ), fraction = 3 )
190
+ view (img )
191
+ print (1.0 / (time () - start ), 'fps, ' , (fcnt - 2 ) / (time () - start0 ), 'average fps' )
192
+ start = time ()
193
+ if fcnt == 2 :
194
+ start0 = time ()
0 commit comments