view org/vision.org @ 212:8e9825c38941

writing intro for vision.org
author Robert McIntyre <rlm@mit.edu>
date Thu, 09 Feb 2012 07:39:21 -0700
parents ac158a976443
children 319963720179
line wrap: on
line source
1 #+title: Simulated Sense of Sight
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description: Simulated sight for AI research using JMonkeyEngine3 and clojure
5 #+keywords: computer vision, jMonkeyEngine3, clojure
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
8 #+babel: :mkdirp yes :noweb yes :exports both
10 * Vision
13 Vision is one of the most important senses for humans, so I need to
14 build a simulated sense of vision for my AI. I will do this with
15 simulated eyes. Each eye can be independely moved and should see its
16 own version of the world depending on where it is.
18 Making these simulated eyes a reality is fairly simple bacause
19 jMonkeyEngine already conatains extensive support for multiple views
20 of the same 3D simulated world. The reason jMonkeyEngine has this
21 support is because the support is necessary to create games with
22 split-screen views. Multiple views are also used to create efficient
23 pseudo-reflections by rendering the scene from a certain perspective
24 and then projecting it back onto a surface in the 3D world.
26 #+caption: jMonkeyEngine supports multiple views to enable split-screen games, like GoldenEye
27 [[../images/goldeneye-4-player.png]]
31 Make the continuation in scene-processor take FrameBuffer,
32 byte-buffer, BufferedImage already sized to the correct
33 dimensions. the continuation will decide wether to "mix" them
34 into the BufferedImage, lazily ignore them, or mix them halfway
35 and call c/graphics card routines.
37 (vision creature) will take an optional :skip argument which will
38 inform the continuations in scene processor to skip the given
39 number of cycles 0 means that no cycles will be skipped.
41 (vision creature) will return [init-functions sensor-functions].
42 The init-functions are each single-arg functions that take the
43 world and register the cameras and must each be called before the
44 corresponding sensor-functions. Each init-function returns the
45 viewport for that eye which can be manipulated, saved, etc. Each
46 sensor-function is a thunk and will return data in the same
47 format as the tactile-sensor functions the structure is
48 [topology, sensor-data]. Internally, these sensor-functions
49 maintain a reference to sensor-data which is periodically updated
50 by the continuation function established by its init-function.
51 They can be queried every cycle, but their information may not
52 necessairly be different every cycle.
54 Each eye in the creature in blender will work the same way as
55 joints -- a zero dimensional object with no geometry whose local
56 coordinate system determines the orientation of the resulting
57 eye. All eyes will have a parent named "eyes" just as all joints
58 have a parent named "joints". The resulting camera will be a
59 ChaseCamera or a CameraNode bound to the geo that is closest to
60 the eye marker. The eye marker will contain the metadata for the
61 eye, and will be moved by it's bound geometry. The dimensions of
62 the eye's camera are equal to the dimensions of the eye's "UV"
63 map.
65 #+name: eyes
66 #+begin_src clojure
67 (ns cortex.vision
68 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
69 eyes from different positions to observe the same world, and pass
70 the observed data to any arbitray function. Automatically reads
71 eye-nodes from specially prepared blender files and instanttiates
72 them in the world as actual eyes."
73 {:author "Robert McIntyre"}
74 (:use (cortex world sense util))
75 (:use clojure.contrib.def)
76 (:import com.jme3.post.SceneProcessor)
77 (:import (com.jme3.util BufferUtils Screenshots))
78 (:import java.nio.ByteBuffer)
79 (:import java.awt.image.BufferedImage)
80 (:import (com.jme3.renderer ViewPort Camera))
81 (:import com.jme3.math.ColorRGBA)
82 (:import com.jme3.renderer.Renderer)
83 (:import com.jme3.app.Application)
84 (:import com.jme3.texture.FrameBuffer)
85 (:import (com.jme3.scene Node Spatial)))
87 (defn vision-pipeline
88 "Create a SceneProcessor object which wraps a vision processing
89 continuation function. The continuation is a function that takes
90 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],
91 each of which has already been appropiately sized."
92 [continuation]
93 (let [byte-buffer (atom nil)
94 renderer (atom nil)
95 image (atom nil)]
96 (proxy [SceneProcessor] []
97 (initialize
98 [renderManager viewPort]
99 (let [cam (.getCamera viewPort)
100 width (.getWidth cam)
101 height (.getHeight cam)]
102 (reset! renderer (.getRenderer renderManager))
103 (reset! byte-buffer
104 (BufferUtils/createByteBuffer
105 (* width height 4)))
106 (reset! image (BufferedImage.
107 width height
108 BufferedImage/TYPE_4BYTE_ABGR))))
109 (isInitialized [] (not (nil? @byte-buffer)))
110 (reshape [_ _ _])
111 (preFrame [_])
112 (postQueue [_])
113 (postFrame
114 [#^FrameBuffer fb]
115 (.clear @byte-buffer)
116 (continuation @renderer fb @byte-buffer @image))
117 (cleanup []))))
119 (defn frameBuffer->byteBuffer!
120 "Transfer the data in the graphics card (Renderer, FrameBuffer) to
121 the CPU (ByteBuffer)."
122 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]
123 (.readFrameBuffer r fb bb) bb)
125 (defn byteBuffer->bufferedImage!
126 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT
127 style ABGR image data and place it in BufferedImage bi."
128 [#^ByteBuffer bb #^BufferedImage bi]
129 (Screenshots/convertScreenShot bb bi) bi)
131 (defn BufferedImage!
132 "Continuation which will grab the buffered image from the materials
133 provided by (vision-pipeline)."
134 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]
135 (byteBuffer->bufferedImage!
136 (frameBuffer->byteBuffer! r fb bb) bi))
138 (defn add-camera!
139 "Add a camera to the world, calling continuation on every frame
140 produced."
141 [#^Application world camera continuation]
142 (let [width (.getWidth camera)
143 height (.getHeight camera)
144 render-manager (.getRenderManager world)
145 viewport (.createMainView render-manager "eye-view" camera)]
146 (doto viewport
147 (.setClearFlags true true true)
148 (.setBackgroundColor ColorRGBA/Black)
149 (.addProcessor (vision-pipeline continuation))
150 (.attachScene (.getRootNode world)))))
152 (defn retina-sensor-profile
153 "Return a map of pixel selection functions to BufferedImages
154 describing the distribution of light-sensitive components of this
155 eye. Each function creates an integer from the rgb values found in
156 the pixel. :red, :green, :blue, :gray are already defined as
157 extracting the red, green, blue, and average components
158 respectively."
159 [#^Spatial eye]
160 (if-let [eye-map (meta-data eye "eye")]
161 (map-vals
162 load-image
163 (eval (read-string eye-map)))))
165 (defn eye-dimensions
166 "Returns [width, height] specified in the metadata of the eye"
167 [#^Spatial eye]
168 (let [dimensions
169 (map #(vector (.getWidth %) (.getHeight %))
170 (vals (retina-sensor-profile eye)))]
171 [(apply max (map first dimensions))
172 (apply max (map second dimensions))]))
174 (defvar
175 ^{:arglists '([creature])}
176 eyes
177 (sense-nodes "eyes")
178 "Return the children of the creature's \"eyes\" node.")
180 (defn add-eye!
181 "Create a Camera centered on the current position of 'eye which
182 follows the closest physical node in 'creature and sends visual
183 data to 'continuation."
184 [#^Node creature #^Spatial eye]
185 (let [target (closest-node creature eye)
186 [cam-width cam-height] (eye-dimensions eye)
187 cam (Camera. cam-width cam-height)]
188 (.setLocation cam (.getWorldTranslation eye))
189 (.setRotation cam (.getWorldRotation eye))
190 (.setFrustumPerspective
191 cam 45 (/ (.getWidth cam) (.getHeight cam))
192 1 1000)
193 (bind-sense target cam)
194 cam))
196 (defvar color-channel-presets
197 {:all 0xFFFFFF
198 :red 0xFF0000
199 :blue 0x0000FF
200 :green 0x00FF00}
201 "Bitmasks for common RGB color channels")
203 (defn vision-fn
204 "Returns a list of functions, each of which will return a color
205 channel's worth of visual information when called inside a running
206 simulation."
207 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
208 (let [retinal-map (retina-sensor-profile eye)
209 camera (add-eye! creature eye)
210 vision-image
211 (atom
212 (BufferedImage. (.getWidth camera)
213 (.getHeight camera)
214 BufferedImage/TYPE_BYTE_BINARY))
215 register-eye!
216 (runonce
217 (fn [world]
218 (add-camera!
219 world camera
220 (let [counter (atom 0)]
221 (fn [r fb bb bi]
222 (if (zero? (rem (swap! counter inc) (inc skip)))
223 (reset! vision-image
224 (BufferedImage! r fb bb bi))))))))]
225 (vec
226 (map
227 (fn [[key image]]
228 (let [whites (white-coordinates image)
229 topology (vec (collapse whites))
230 mask (color-channel-presets key)]
231 (fn [world]
232 (register-eye! world)
233 (vector
234 topology
235 (vec
236 (for [[x y] whites]
237 (bit-and
238 mask (.getRGB @vision-image x y))))))))
239 retinal-map))))
242 ;; TODO maybe should add a viewport-manipulation function to
243 ;; automatically change viewport settings, attach shadow filters, etc.
245 (defn vision!
246 "Returns a function which returns visual sensory data when called
247 inside a running simulation"
248 [#^Node creature & {skip :skip :or {skip 0}}]
249 (reduce
250 concat
251 (for [eye (eyes creature)]
252 (vision-fn creature eye))))
254 (defn view-vision
255 "Creates a function which accepts a list of visual sensor-data and
256 displays each element of the list to the screen."
257 []
258 (view-sense
259 (fn
260 [[coords sensor-data]]
261 (let [image (points->image coords)]
262 (dorun
263 (for [i (range (count coords))]
264 (.setRGB image ((coords i) 0) ((coords i) 1)
265 (sensor-data i))))
266 image))))
268 #+end_src
271 Note the use of continuation passing style for connecting the eye to a
272 function to process the output. You can create any number of eyes, and
273 each of them will see the world from their own =Camera=. Once every
274 frame, the rendered image is copied to a =BufferedImage=, and that
275 data is sent off to the continuation function. Moving the =Camera=
276 which was used to create the eye will change what the eye sees.
278 * Example
280 #+name: test-vision
281 #+begin_src clojure
282 (ns cortex.test.vision
283 (:use (cortex world util vision))
284 (:import java.awt.image.BufferedImage)
285 (:import javax.swing.JPanel)
286 (:import javax.swing.SwingUtilities)
287 (:import java.awt.Dimension)
288 (:import javax.swing.JFrame)
289 (:import com.jme3.math.ColorRGBA)
290 (:import com.jme3.scene.Node)
291 (:import com.jme3.math.Vector3f))
293 (defn test-two-eyes
294 "Testing vision:
295 Tests the vision system by creating two views of the same rotating
296 object from different angles and displaying both of those views in
297 JFrames.
299 You should see a rotating cube, and two windows,
300 each displaying a different view of the cube."
301 []
302 (let [candy
303 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]
304 (world
305 (doto (Node.)
306 (.attachChild candy))
307 {}
308 (fn [world]
309 (let [cam (.clone (.getCamera world))
310 width (.getWidth cam)
311 height (.getHeight cam)]
312 (add-camera! world cam
313 ;;no-op
314 (comp (view-image) BufferedImage!)
315 )
316 (add-camera! world
317 (doto (.clone cam)
318 (.setLocation (Vector3f. -10 0 0))
319 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
320 ;;no-op
321 (comp (view-image) BufferedImage!))
322 ;; This is here to restore the main view
323 ;; after the other views have completed processing
324 (add-camera! world (.getCamera world) no-op)))
325 (fn [world tpf]
326 (.rotate candy (* tpf 0.2) 0 0)))))
327 #+end_src
329 #+results: test-vision
330 : #'cortex.test.vision/test-two-eyes
332 The example code will create two videos of the same rotating object
333 from different angles. It can be used both for stereoscopic vision
334 simulation or for simulating multiple creatures, each with their own
335 sense of vision.
337 - As a neat bonus, this idea behind simulated vision also enables one
338 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
341 * COMMENT Generate Source
342 #+begin_src clojure :tangle ../src/cortex/vision.clj
343 <<eyes>>
344 #+end_src
346 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
347 <<test-vision>>
348 #+end_src