annotate org/vision.org @ 265:e57d8c52f12f

More tweaks to vision.
author Dylan Holmes <ocsenave@gmail.com>
date Mon, 13 Feb 2012 21:53:28 -0600
parents f8227f6d4ac6
children aa3641042958
rev   line source
rlm@34 1 #+title: Simulated Sense of Sight
rlm@23 2 #+author: Robert McIntyre
rlm@23 3 #+email: rlm@mit.edu
rlm@38 4 #+description: Simulated sight for AI research using JMonkeyEngine3 and clojure
rlm@34 5 #+keywords: computer vision, jMonkeyEngine3, clojure
rlm@23 6 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@23 7 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@23 8 #+babel: :mkdirp yes :noweb yes :exports both
rlm@23 9
ocsenave@265 10 # SUGGEST: Call functions by their name, without
ocsenave@265 11 # parentheses. e.g. =add-eye!=, not =(add-eye!)=. The reason for this
ocsenave@265 12 # is that it is potentially easy to confuse the /function/ =f= with its
ocsenave@265 13 # /value/ at a particular point =(f x)=. Mathematicians have this
ocsenave@265 14 # problem with their notation; we don't need it in ours.
ocsenave@265 15
ocsenave@264 16 #* Vision
ocsenave@264 17 * JMonkeyEngine natively supports multiple views of the same world.
ocsenave@264 18
rlm@212 19 Vision is one of the most important senses for humans, so I need to
rlm@212 20 build a simulated sense of vision for my AI. I will do this with
rlm@212 21 simulated eyes. Each eye can be independely moved and should see its
rlm@212 22 own version of the world depending on where it is.
rlm@212 23
rlm@218 24 Making these simulated eyes a reality is simple bacause jMonkeyEngine
rlm@218 25 already conatains extensive support for multiple views of the same 3D
rlm@218 26 simulated world. The reason jMonkeyEngine has this support is because
rlm@218 27 the support is necessary to create games with split-screen
rlm@218 28 views. Multiple views are also used to create efficient
rlm@212 29 pseudo-reflections by rendering the scene from a certain perspective
rlm@212 30 and then projecting it back onto a surface in the 3D world.
rlm@212 31
rlm@218 32 #+caption: jMonkeyEngine supports multiple views to enable split-screen games, like GoldenEye, which was one of the first games to use split-screen views.
rlm@212 33 [[../images/goldeneye-4-player.png]]
rlm@212 34
ocsenave@264 35 ** =ViewPorts=, =SceneProcessors=, and the =RenderManager=.
ocsenave@264 36 # =Viewports= are cameras; =RenderManger= takes snapshots each frame.
ocsenave@264 37 #* A Brief Description of jMonkeyEngine's Rendering Pipeline
rlm@212 38
rlm@213 39 jMonkeyEngine allows you to create a =ViewPort=, which represents a
rlm@213 40 view of the simulated world. You can create as many of these as you
rlm@213 41 want. Every frame, the =RenderManager= iterates through each
rlm@213 42 =ViewPort=, rendering the scene in the GPU. For each =ViewPort= there
rlm@213 43 is a =FrameBuffer= which represents the rendered image in the GPU.
rlm@151 44
ocsenave@262 45 #+caption: =ViewPorts= are cameras in the world. During each frame, the =Rendermanager= records a snapshot of what each view is currently seeing.
ocsenave@265 46 #+ATTR_HTML: width="400"
ocsenave@262 47 [[../images/diagram_rendermanager.png]]
ocsenave@262 48
rlm@213 49 Each =ViewPort= can have any number of attached =SceneProcessor=
rlm@213 50 objects, which are called every time a new frame is rendered. A
rlm@219 51 =SceneProcessor= recieves its =ViewPort's= =FrameBuffer= and can do
rlm@219 52 whatever it wants to the data. Often this consists of invoking GPU
rlm@219 53 specific operations on the rendered image. The =SceneProcessor= can
rlm@219 54 also copy the GPU image data to RAM and process it with the CPU.
rlm@151 55
ocsenave@264 56 ** From Views to Vision
ocsenave@264 57 # Appropriating Views for Vision.
rlm@151 58
ocsenave@264 59 Each eye in the simulated creature needs its own =ViewPort= so that
rlm@213 60 it can see the world from its own perspective. To this =ViewPort=, I
rlm@214 61 add a =SceneProcessor= that feeds the visual data to any arbitray
rlm@213 62 continuation function for further processing. That continuation
rlm@213 63 function may perform both CPU and GPU operations on the data. To make
rlm@213 64 this easy for the continuation function, the =SceneProcessor=
rlm@213 65 maintains appropriatly sized buffers in RAM to hold the data. It does
rlm@218 66 not do any copying from the GPU to the CPU itself because it is a slow
rlm@218 67 operation.
rlm@214 68
rlm@213 69 #+name: pipeline-1
rlm@213 70 #+begin_src clojure
rlm@113 71 (defn vision-pipeline
rlm@34 72 "Create a SceneProcessor object which wraps a vision processing
rlm@113 73 continuation function. The continuation is a function that takes
rlm@113 74 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],
rlm@113 75 each of which has already been appropiately sized."
rlm@23 76 [continuation]
rlm@23 77 (let [byte-buffer (atom nil)
rlm@113 78 renderer (atom nil)
rlm@113 79 image (atom nil)]
rlm@23 80 (proxy [SceneProcessor] []
rlm@23 81 (initialize
rlm@23 82 [renderManager viewPort]
rlm@23 83 (let [cam (.getCamera viewPort)
rlm@23 84 width (.getWidth cam)
rlm@23 85 height (.getHeight cam)]
rlm@23 86 (reset! renderer (.getRenderer renderManager))
rlm@23 87 (reset! byte-buffer
rlm@23 88 (BufferUtils/createByteBuffer
rlm@113 89 (* width height 4)))
rlm@113 90 (reset! image (BufferedImage.
rlm@113 91 width height
rlm@113 92 BufferedImage/TYPE_4BYTE_ABGR))))
rlm@23 93 (isInitialized [] (not (nil? @byte-buffer)))
rlm@23 94 (reshape [_ _ _])
rlm@23 95 (preFrame [_])
rlm@23 96 (postQueue [_])
rlm@23 97 (postFrame
rlm@23 98 [#^FrameBuffer fb]
rlm@23 99 (.clear @byte-buffer)
rlm@113 100 (continuation @renderer fb @byte-buffer @image))
rlm@23 101 (cleanup []))))
rlm@213 102 #+end_src
rlm@213 103
rlm@213 104 The continuation function given to =(vision-pipeline)= above will be
rlm@213 105 given a =Renderer= and three containers for image data. The
rlm@218 106 =FrameBuffer= references the GPU image data, but the pixel data can
rlm@218 107 not be used directly on the CPU. The =ByteBuffer= and =BufferedImage=
rlm@219 108 are initially "empty" but are sized to hold the data in the
rlm@213 109 =FrameBuffer=. I call transfering the GPU image data to the CPU
rlm@213 110 structures "mixing" the image data. I have provided three functions to
rlm@213 111 do this mixing.
rlm@213 112
rlm@213 113 #+name: pipeline-2
rlm@213 114 #+begin_src clojure
rlm@113 115 (defn frameBuffer->byteBuffer!
rlm@113 116 "Transfer the data in the graphics card (Renderer, FrameBuffer) to
rlm@113 117 the CPU (ByteBuffer)."
rlm@113 118 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]
rlm@113 119 (.readFrameBuffer r fb bb) bb)
rlm@113 120
rlm@113 121 (defn byteBuffer->bufferedImage!
rlm@113 122 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT
rlm@113 123 style ABGR image data and place it in BufferedImage bi."
rlm@113 124 [#^ByteBuffer bb #^BufferedImage bi]
rlm@113 125 (Screenshots/convertScreenShot bb bi) bi)
rlm@113 126
rlm@113 127 (defn BufferedImage!
rlm@113 128 "Continuation which will grab the buffered image from the materials
rlm@113 129 provided by (vision-pipeline)."
rlm@113 130 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]
rlm@113 131 (byteBuffer->bufferedImage!
rlm@113 132 (frameBuffer->byteBuffer! r fb bb) bi))
rlm@213 133 #+end_src
rlm@112 134
rlm@213 135 Note that it is possible to write vision processing algorithms
rlm@213 136 entirely in terms of =BufferedImage= inputs. Just compose that
rlm@213 137 =BufferedImage= algorithm with =(BufferedImage!)=. However, a vision
rlm@213 138 processing algorithm that is entirely hosted on the GPU does not have
rlm@213 139 to pay for this convienence.
rlm@213 140
rlm@214 141 * COMMENT asdasd
rlm@213 142
rlm@213 143 (vision creature) will take an optional :skip argument which will
rlm@213 144 inform the continuations in scene processor to skip the given
rlm@213 145 number of cycles 0 means that no cycles will be skipped.
rlm@213 146
rlm@213 147 (vision creature) will return [init-functions sensor-functions].
rlm@213 148 The init-functions are each single-arg functions that take the
rlm@213 149 world and register the cameras and must each be called before the
rlm@213 150 corresponding sensor-functions. Each init-function returns the
rlm@213 151 viewport for that eye which can be manipulated, saved, etc. Each
rlm@213 152 sensor-function is a thunk and will return data in the same
rlm@213 153 format as the tactile-sensor functions the structure is
rlm@213 154 [topology, sensor-data]. Internally, these sensor-functions
rlm@213 155 maintain a reference to sensor-data which is periodically updated
rlm@213 156 by the continuation function established by its init-function.
rlm@213 157 They can be queried every cycle, but their information may not
rlm@213 158 necessairly be different every cycle.
rlm@213 159
ocsenave@265 160 # * Optical sensor arrays are described as images and stored as metadata.
ocsenave@265 161 * Optical sensor arrays are described with images and referenced with metadata
rlm@214 162 The vision pipeline described above handles the flow of rendered
rlm@214 163 images. Now, we need simulated eyes to serve as the source of these
rlm@214 164 images.
rlm@214 165
rlm@214 166 An eye is described in blender in the same way as a joint. They are
rlm@214 167 zero dimensional empty objects with no geometry whose local coordinate
rlm@214 168 system determines the orientation of the resulting eye. All eyes are
rlm@214 169 childern of a parent node named "eyes" just as all joints have a
rlm@214 170 parent named "joints". An eye binds to the nearest physical object
rlm@214 171 with =(bind-sense=).
rlm@214 172
rlm@214 173 #+name: add-eye
rlm@214 174 #+begin_src clojure
rlm@215 175 (in-ns 'cortex.vision)
rlm@215 176
rlm@214 177 (defn add-eye!
rlm@214 178 "Create a Camera centered on the current position of 'eye which
rlm@214 179 follows the closest physical node in 'creature and sends visual
rlm@215 180 data to 'continuation. The camera will point in the X direction and
rlm@215 181 use the Z vector as up as determined by the rotation of these
rlm@215 182 vectors in blender coordinate space. Use XZY rotation for the node
rlm@215 183 in blender."
rlm@214 184 [#^Node creature #^Spatial eye]
rlm@214 185 (let [target (closest-node creature eye)
rlm@214 186 [cam-width cam-height] (eye-dimensions eye)
rlm@215 187 cam (Camera. cam-width cam-height)
rlm@215 188 rot (.getWorldRotation eye)]
rlm@214 189 (.setLocation cam (.getWorldTranslation eye))
rlm@218 190 (.lookAtDirection
rlm@218 191 cam ; this part is not a mistake and
rlm@218 192 (.mult rot Vector3f/UNIT_X) ; is consistent with using Z in
rlm@218 193 (.mult rot Vector3f/UNIT_Y)) ; blender as the UP vector.
rlm@214 194 (.setFrustumPerspective
rlm@215 195 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000)
rlm@215 196 (bind-sense target cam) cam))
rlm@214 197 #+end_src
rlm@214 198
rlm@214 199 Here, the camera is created based on metadata on the eye-node and
rlm@214 200 attached to the nearest physical object with =(bind-sense)=
rlm@214 201
rlm@214 202
rlm@214 203 ** The Retina
rlm@214 204
rlm@214 205 An eye is a surface (the retina) which contains many discrete sensors
rlm@218 206 to detect light. These sensors have can have different light-sensing
rlm@214 207 properties. In humans, each discrete sensor is sensitive to red,
rlm@214 208 blue, green, or gray. These different types of sensors can have
rlm@214 209 different spatial distributions along the retina. In humans, there is
rlm@214 210 a fovea in the center of the retina which has a very high density of
rlm@214 211 color sensors, and a blind spot which has no sensors at all. Sensor
rlm@219 212 density decreases in proportion to distance from the fovea.
rlm@214 213
rlm@214 214 I want to be able to model any retinal configuration, so my eye-nodes
rlm@214 215 in blender contain metadata pointing to images that describe the
rlm@214 216 percise position of the individual sensors using white pixels. The
rlm@214 217 meta-data also describes the percise sensitivity to light that the
rlm@214 218 sensors described in the image have. An eye can contain any number of
rlm@214 219 these images. For example, the metadata for an eye might look like
rlm@214 220 this:
rlm@214 221
rlm@214 222 #+begin_src clojure
rlm@214 223 {0xFF0000 "Models/test-creature/retina-small.png"}
rlm@214 224 #+end_src
rlm@214 225
rlm@214 226 #+caption: The retinal profile image "Models/test-creature/retina-small.png". White pixels are photo-sensitive elements. The distribution of white pixels is denser in the middle and falls off at the edges and is inspired by the human retina.
rlm@214 227 [[../assets/Models/test-creature/retina-small.png]]
rlm@214 228
rlm@214 229 Together, the number 0xFF0000 and the image image above describe the
rlm@214 230 placement of red-sensitive sensory elements.
rlm@214 231
rlm@214 232 Meta-data to very crudely approximate a human eye might be something
rlm@214 233 like this:
rlm@214 234
rlm@214 235 #+begin_src clojure
rlm@214 236 (let [retinal-profile "Models/test-creature/retina-small.png"]
rlm@214 237 {0xFF0000 retinal-profile
rlm@214 238 0x00FF00 retinal-profile
rlm@214 239 0x0000FF retinal-profile
rlm@214 240 0xFFFFFF retinal-profile})
rlm@214 241 #+end_src
rlm@214 242
rlm@214 243 The numbers that serve as keys in the map determine a sensor's
rlm@214 244 relative sensitivity to the channels red, green, and blue. These
rlm@218 245 sensitivity values are packed into an integer in the order =|_|R|G|B|=
rlm@218 246 in 8-bit fields. The RGB values of a pixel in the image are added
rlm@214 247 together with these sensitivities as linear weights. Therfore,
rlm@214 248 0xFF0000 means sensitive to red only while 0xFFFFFF means sensitive to
rlm@214 249 all colors equally (gray).
rlm@214 250
rlm@214 251 For convienence I've defined a few symbols for the more common
rlm@214 252 sensitivity values.
rlm@214 253
rlm@214 254 #+name: sensitivity
rlm@214 255 #+begin_src clojure
rlm@214 256 (defvar sensitivity-presets
rlm@214 257 {:all 0xFFFFFF
rlm@214 258 :red 0xFF0000
rlm@214 259 :blue 0x0000FF
rlm@214 260 :green 0x00FF00}
rlm@214 261 "Retinal sensitivity presets for sensors that extract one channel
rlm@219 262 (:red :blue :green) or average all channels (:all)")
rlm@214 263 #+end_src
rlm@214 264
rlm@214 265 ** Metadata Processing
rlm@214 266
rlm@214 267 =(retina-sensor-profile)= extracts a map from the eye-node in the same
rlm@214 268 format as the example maps above. =(eye-dimensions)= finds the
rlm@219 269 dimensions of the smallest image required to contain all the retinal
rlm@214 270 sensor maps.
rlm@214 271
rlm@216 272 #+name: retina
rlm@214 273 #+begin_src clojure
rlm@214 274 (defn retina-sensor-profile
rlm@214 275 "Return a map of pixel sensitivity numbers to BufferedImages
rlm@214 276 describing the distribution of light-sensitive components of this
rlm@214 277 eye. :red, :green, :blue, :gray are already defined as extracting
rlm@214 278 the red, green, blue, and average components respectively."
rlm@214 279 [#^Spatial eye]
rlm@214 280 (if-let [eye-map (meta-data eye "eye")]
rlm@214 281 (map-vals
rlm@214 282 load-image
rlm@214 283 (eval (read-string eye-map)))))
rlm@214 284
rlm@218 285 (defn eye-dimensions
rlm@218 286 "Returns [width, height] determined by the metadata of the eye."
rlm@214 287 [#^Spatial eye]
rlm@214 288 (let [dimensions
rlm@214 289 (map #(vector (.getWidth %) (.getHeight %))
rlm@214 290 (vals (retina-sensor-profile eye)))]
rlm@214 291 [(apply max (map first dimensions))
rlm@214 292 (apply max (map second dimensions))]))
rlm@214 293 #+end_src
rlm@214 294
ocsenave@265 295 * Importing and parsing descriptions of eyes.
rlm@214 296 First off, get the children of the "eyes" empty node to find all the
rlm@214 297 eyes the creature has.
rlm@216 298 #+name: eye-node
rlm@214 299 #+begin_src clojure
rlm@214 300 (defvar
rlm@214 301 ^{:arglists '([creature])}
rlm@214 302 eyes
rlm@214 303 (sense-nodes "eyes")
rlm@214 304 "Return the children of the creature's \"eyes\" node.")
rlm@214 305 #+end_src
rlm@214 306
rlm@215 307 Then, add the camera created by =(add-eye!)= to the simulation by
rlm@215 308 creating a new viewport.
rlm@214 309
rlm@216 310 #+name: add-camera
rlm@213 311 #+begin_src clojure
rlm@169 312 (defn add-camera!
rlm@169 313 "Add a camera to the world, calling continuation on every frame
rlm@34 314 produced."
rlm@167 315 [#^Application world camera continuation]
rlm@23 316 (let [width (.getWidth camera)
rlm@23 317 height (.getHeight camera)
rlm@23 318 render-manager (.getRenderManager world)
rlm@23 319 viewport (.createMainView render-manager "eye-view" camera)]
rlm@23 320 (doto viewport
rlm@23 321 (.setClearFlags true true true)
rlm@112 322 (.setBackgroundColor ColorRGBA/Black)
rlm@113 323 (.addProcessor (vision-pipeline continuation))
rlm@23 324 (.attachScene (.getRootNode world)))))
rlm@215 325 #+end_src
rlm@151 326
rlm@151 327
rlm@218 328 The eye's continuation function should register the viewport with the
rlm@218 329 simulation the first time it is called, use the CPU to extract the
rlm@215 330 appropriate pixels from the rendered image and weight them by each
rlm@218 331 sensor's sensitivity. I have the option to do this processing in
rlm@218 332 native code for a slight gain in speed. I could also do it in the GPU
rlm@218 333 for a massive gain in speed. =(vision-kernel)= generates a list of
rlm@218 334 such continuation functions, one for each channel of the eye.
rlm@151 335
rlm@216 336 #+name: kernel
rlm@215 337 #+begin_src clojure
rlm@215 338 (in-ns 'cortex.vision)
rlm@151 339
rlm@215 340 (defrecord attached-viewport [vision-fn viewport-fn]
rlm@215 341 clojure.lang.IFn
rlm@215 342 (invoke [this world] (vision-fn world))
rlm@215 343 (applyTo [this args] (apply vision-fn args)))
rlm@151 344
rlm@216 345 (defn pixel-sense [sensitivity pixel]
rlm@216 346 (let [s-r (bit-shift-right (bit-and 0xFF0000 sensitivity) 16)
rlm@216 347 s-g (bit-shift-right (bit-and 0x00FF00 sensitivity) 8)
rlm@216 348 s-b (bit-and 0x0000FF sensitivity)
rlm@216 349
rlm@216 350 p-r (bit-shift-right (bit-and 0xFF0000 pixel) 16)
rlm@216 351 p-g (bit-shift-right (bit-and 0x00FF00 pixel) 8)
rlm@216 352 p-b (bit-and 0x0000FF pixel)
rlm@216 353
rlm@216 354 total-sensitivity (* 255 (+ s-r s-g s-b))]
rlm@216 355 (float (/ (+ (* s-r p-r)
rlm@216 356 (* s-g p-g)
rlm@216 357 (* s-b p-b))
rlm@216 358 total-sensitivity))))
rlm@216 359
rlm@215 360 (defn vision-kernel
rlm@171 361 "Returns a list of functions, each of which will return a color
rlm@171 362 channel's worth of visual information when called inside a running
rlm@171 363 simulation."
rlm@151 364 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
rlm@169 365 (let [retinal-map (retina-sensor-profile eye)
rlm@169 366 camera (add-eye! creature eye)
rlm@151 367 vision-image
rlm@151 368 (atom
rlm@151 369 (BufferedImage. (.getWidth camera)
rlm@151 370 (.getHeight camera)
rlm@170 371 BufferedImage/TYPE_BYTE_BINARY))
rlm@170 372 register-eye!
rlm@170 373 (runonce
rlm@170 374 (fn [world]
rlm@170 375 (add-camera!
rlm@170 376 world camera
rlm@170 377 (let [counter (atom 0)]
rlm@170 378 (fn [r fb bb bi]
rlm@170 379 (if (zero? (rem (swap! counter inc) (inc skip)))
rlm@170 380 (reset! vision-image
rlm@170 381 (BufferedImage! r fb bb bi))))))))]
rlm@151 382 (vec
rlm@151 383 (map
rlm@151 384 (fn [[key image]]
rlm@151 385 (let [whites (white-coordinates image)
rlm@151 386 topology (vec (collapse whites))
rlm@216 387 sensitivity (sensitivity-presets key key)]
rlm@215 388 (attached-viewport.
rlm@215 389 (fn [world]
rlm@215 390 (register-eye! world)
rlm@215 391 (vector
rlm@215 392 topology
rlm@215 393 (vec
rlm@215 394 (for [[x y] whites]
rlm@216 395 (pixel-sense
rlm@216 396 sensitivity
rlm@216 397 (.getRGB @vision-image x y))))))
rlm@215 398 register-eye!)))
rlm@215 399 retinal-map))))
rlm@151 400
rlm@215 401 (defn gen-fix-display
rlm@215 402 "Create a function to call to restore a simulation's display when it
rlm@215 403 is disrupted by a Viewport."
rlm@215 404 []
rlm@215 405 (runonce
rlm@215 406 (fn [world]
rlm@215 407 (add-camera! world (.getCamera world) no-op))))
rlm@215 408 #+end_src
rlm@170 409
rlm@215 410 Note that since each of the functions generated by =(vision-kernel)=
rlm@215 411 shares the same =(register-eye!)= function, the eye will be registered
rlm@215 412 only once the first time any of the functions from the list returned
rlm@215 413 by =(vision-kernel)= is called. Each of the functions returned by
rlm@215 414 =(vision-kernel)= also allows access to the =Viewport= through which
rlm@215 415 it recieves images.
rlm@215 416
rlm@215 417 The in-game display can be disrupted by all the viewports that the
rlm@215 418 functions greated by =(vision-kernel)= add. This doesn't affect the
rlm@215 419 simulation or the simulated senses, but can be annoying.
rlm@215 420 =(gen-fix-display)= restores the in-simulation display.
rlm@215 421
ocsenave@265 422 ** The =vision!= function creates sensory probes.
rlm@215 423
rlm@218 424 All the hard work has been done; all that remains is to apply
rlm@215 425 =(vision-kernel)= to each eye in the creature and gather the results
rlm@215 426 into one list of functions.
rlm@215 427
rlm@216 428 #+name: main
rlm@215 429 #+begin_src clojure
rlm@170 430 (defn vision!
rlm@170 431 "Returns a function which returns visual sensory data when called
rlm@218 432 inside a running simulation."
rlm@151 433 [#^Node creature & {skip :skip :or {skip 0}}]
rlm@151 434 (reduce
rlm@170 435 concat
rlm@167 436 (for [eye (eyes creature)]
rlm@215 437 (vision-kernel creature eye))))
rlm@215 438 #+end_src
rlm@151 439
ocsenave@265 440 ** Displaying visual data for debugging.
ocsenave@265 441 # Visualization of Vision. Maybe less alliteration would be better.
rlm@215 442 It's vital to have a visual representation for each sense. Here I use
rlm@215 443 =(view-sense)= to construct a function that will create a display for
rlm@215 444 visual data.
rlm@215 445
rlm@216 446 #+name: display
rlm@215 447 #+begin_src clojure
rlm@216 448 (in-ns 'cortex.vision)
rlm@216 449
rlm@189 450 (defn view-vision
rlm@189 451 "Creates a function which accepts a list of visual sensor-data and
rlm@189 452 displays each element of the list to the screen."
rlm@189 453 []
rlm@188 454 (view-sense
rlm@188 455 (fn
rlm@188 456 [[coords sensor-data]]
rlm@188 457 (let [image (points->image coords)]
rlm@188 458 (dorun
rlm@188 459 (for [i (range (count coords))]
rlm@188 460 (.setRGB image ((coords i) 0) ((coords i) 1)
rlm@216 461 (gray (int (* 255 (sensor-data i)))))))
rlm@189 462 image))))
rlm@34 463 #+end_src
rlm@23 464
ocsenave@264 465 * Demonstrations
ocsenave@264 466 ** Demonstrating the vision pipeline.
rlm@23 467
rlm@215 468 This is a basic test for the vision system. It only tests the
ocsenave@264 469 vision-pipeline and does not deal with loading eyes from a blender
rlm@215 470 file. The code creates two videos of the same rotating cube from
rlm@215 471 different angles.
rlm@23 472
rlm@215 473 #+name: test-1
rlm@23 474 #+begin_src clojure
rlm@215 475 (in-ns 'cortex.test.vision)
rlm@23 476
rlm@219 477 (defn test-pipeline
rlm@69 478 "Testing vision:
rlm@69 479 Tests the vision system by creating two views of the same rotating
rlm@69 480 object from different angles and displaying both of those views in
rlm@69 481 JFrames.
rlm@69 482
rlm@69 483 You should see a rotating cube, and two windows,
rlm@69 484 each displaying a different view of the cube."
rlm@36 485 []
rlm@58 486 (let [candy
rlm@58 487 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]
rlm@112 488 (world
rlm@112 489 (doto (Node.)
rlm@112 490 (.attachChild candy))
rlm@112 491 {}
rlm@112 492 (fn [world]
rlm@112 493 (let [cam (.clone (.getCamera world))
rlm@112 494 width (.getWidth cam)
rlm@112 495 height (.getHeight cam)]
rlm@169 496 (add-camera! world cam
rlm@215 497 (comp
rlm@215 498 (view-image
rlm@215 499 (File. "/home/r/proj/cortex/render/vision/1"))
rlm@215 500 BufferedImage!))
rlm@169 501 (add-camera! world
rlm@112 502 (doto (.clone cam)
rlm@112 503 (.setLocation (Vector3f. -10 0 0))
rlm@112 504 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
rlm@215 505 (comp
rlm@215 506 (view-image
rlm@215 507 (File. "/home/r/proj/cortex/render/vision/2"))
rlm@215 508 BufferedImage!))
rlm@112 509 ;; This is here to restore the main view
rlm@112 510 ;; after the other views have completed processing
rlm@169 511 (add-camera! world (.getCamera world) no-op)))
rlm@112 512 (fn [world tpf]
rlm@112 513 (.rotate candy (* tpf 0.2) 0 0)))))
rlm@23 514 #+end_src
rlm@23 515
rlm@215 516 #+begin_html
rlm@215 517 <div class="figure">
rlm@215 518 <video controls="controls" width="755">
rlm@215 519 <source src="../video/spinning-cube.ogg" type="video/ogg"
rlm@215 520 preload="none" poster="../images/aurellem-1280x480.png" />
rlm@215 521 </video>
rlm@215 522 <p>A rotating cube viewed from two different perspectives.</p>
rlm@215 523 </div>
rlm@215 524 #+end_html
rlm@215 525
rlm@215 526 Creating multiple eyes like this can be used for stereoscopic vision
rlm@215 527 simulation in a single creature or for simulating multiple creatures,
rlm@215 528 each with their own sense of vision.
ocsenave@264 529 ** Demonstrating eye import and parsing.
rlm@215 530
rlm@218 531 To the worm from the last post, I add a new node that describes its
rlm@215 532 eyes.
rlm@215 533
rlm@215 534 #+attr_html: width=755
rlm@215 535 #+caption: The worm with newly added empty nodes describing a single eye.
rlm@215 536 [[../images/worm-with-eye.png]]
rlm@215 537
rlm@215 538 The node highlighted in yellow is the root level "eyes" node. It has
rlm@218 539 a single child, highlighted in orange, which describes a single
rlm@218 540 eye. This is the "eye" node. It is placed so that the worm will have
rlm@218 541 an eye located in the center of the flat portion of its lower
rlm@218 542 hemispherical section.
rlm@218 543
rlm@218 544 The two nodes which are not highlighted describe the single joint of
rlm@218 545 the worm.
rlm@215 546
rlm@215 547 The metadata of the eye-node is:
rlm@215 548
rlm@215 549 #+begin_src clojure :results verbatim :exports both
rlm@215 550 (cortex.sense/meta-data
rlm@218 551 (.getChild (.getChild (cortex.test.body/worm) "eyes") "eye") "eye")
rlm@215 552 #+end_src
rlm@215 553
rlm@215 554 #+results:
rlm@215 555 : "(let [retina \"Models/test-creature/retina-small.png\"]
rlm@215 556 : {:all retina :red retina :green retina :blue retina})"
rlm@215 557
rlm@215 558 This is the approximation to the human eye described earlier.
rlm@215 559
rlm@216 560 #+name: test-2
rlm@215 561 #+begin_src clojure
rlm@215 562 (in-ns 'cortex.test.vision)
rlm@215 563
rlm@216 564 (defn change-color [obj color]
rlm@216 565 (println-repl obj)
rlm@216 566 (if obj
rlm@216 567 (.setColor (.getMaterial obj) "Color" color)))
rlm@216 568
rlm@216 569 (defn colored-cannon-ball [color]
rlm@216 570 (comp #(change-color % color)
rlm@216 571 (fire-cannon-ball)))
rlm@215 572
rlm@236 573 (defn test-worm-vision [record]
rlm@215 574 (let [the-worm (doto (worm)(body!))
rlm@215 575 vision (vision! the-worm)
rlm@215 576 vision-display (view-vision)
rlm@215 577 fix-display (gen-fix-display)
rlm@215 578 me (sphere 0.5 :color ColorRGBA/Blue :physical? false)
rlm@215 579 x-axis
rlm@215 580 (box 1 0.01 0.01 :physical? false :color ColorRGBA/Red
rlm@215 581 :position (Vector3f. 0 -5 0))
rlm@215 582 y-axis
rlm@215 583 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green
rlm@215 584 :position (Vector3f. 0 -5 0))
rlm@215 585 z-axis
rlm@215 586 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue
rlm@216 587 :position (Vector3f. 0 -5 0))
rlm@216 588 timer (RatchetTimer. 60)]
rlm@215 589
rlm@215 590 (world (nodify [(floor) the-worm x-axis y-axis z-axis me])
rlm@216 591 (assoc standard-debug-controls
rlm@216 592 "key-r" (colored-cannon-ball ColorRGBA/Red)
rlm@216 593 "key-b" (colored-cannon-ball ColorRGBA/Blue)
rlm@216 594 "key-g" (colored-cannon-ball ColorRGBA/Green))
rlm@215 595 (fn [world]
rlm@215 596 (light-up-everything world)
rlm@216 597 (speed-up world)
rlm@216 598 (.setTimer world timer)
rlm@216 599 (display-dialated-time world timer)
rlm@215 600 ;; add a view from the worm's perspective
rlm@236 601 (if record
rlm@236 602 (Capture/captureVideo
rlm@236 603 world
rlm@236 604 (File.
rlm@236 605 "/home/r/proj/cortex/render/worm-vision/main-view")))
rlm@236 606
rlm@215 607 (add-camera!
rlm@215 608 world
rlm@215 609 (add-eye! the-worm
rlm@215 610 (.getChild
rlm@215 611 (.getChild the-worm "eyes") "eye"))
rlm@215 612 (comp
rlm@215 613 (view-image
rlm@236 614 (if record
rlm@236 615 (File.
rlm@236 616 "/home/r/proj/cortex/render/worm-vision/worm-view")))
rlm@215 617 BufferedImage!))
rlm@236 618
rlm@236 619 (set-gravity world Vector3f/ZERO))
rlm@216 620
rlm@215 621 (fn [world _ ]
rlm@215 622 (.setLocalTranslation me (.getLocation (.getCamera world)))
rlm@215 623 (vision-display
rlm@215 624 (map #(% world) vision)
rlm@236 625 (if record (File. "/home/r/proj/cortex/render/worm-vision")))
rlm@215 626 (fix-display world)))))
rlm@215 627 #+end_src
rlm@215 628
rlm@218 629 The world consists of the worm and a flat gray floor. I can shoot red,
rlm@218 630 green, blue and white cannonballs at the worm. The worm is initially
rlm@218 631 looking down at the floor, and there is no gravity. My perspective
rlm@218 632 (the Main View), the worm's perspective (Worm View) and the 4 sensor
rlm@218 633 channels that comprise the worm's eye are all saved frame-by-frame to
rlm@218 634 disk.
rlm@218 635
rlm@218 636 * Demonstration of Vision
rlm@218 637 #+begin_html
rlm@218 638 <div class="figure">
rlm@218 639 <video controls="controls" width="755">
rlm@218 640 <source src="../video/worm-vision.ogg" type="video/ogg"
rlm@218 641 preload="none" poster="../images/aurellem-1280x480.png" />
rlm@218 642 </video>
rlm@218 643 <p>Simulated Vision in a Virtual Environment</p>
rlm@218 644 </div>
rlm@218 645 #+end_html
rlm@218 646
rlm@218 647 ** Generate the Worm Video from Frames
rlm@216 648 #+name: magick2
rlm@216 649 #+begin_src clojure
rlm@216 650 (ns cortex.video.magick2
rlm@216 651 (:import java.io.File)
rlm@216 652 (:use clojure.contrib.shell-out))
rlm@216 653
rlm@216 654 (defn images [path]
rlm@216 655 (sort (rest (file-seq (File. path)))))
rlm@216 656
rlm@216 657 (def base "/home/r/proj/cortex/render/worm-vision/")
rlm@216 658
rlm@216 659 (defn pics [file]
rlm@216 660 (images (str base file)))
rlm@216 661
rlm@216 662 (defn combine-images []
rlm@216 663 (let [main-view (pics "main-view")
rlm@216 664 worm-view (pics "worm-view")
rlm@216 665 blue (pics "0")
rlm@216 666 green (pics "1")
rlm@216 667 red (pics "2")
rlm@216 668 gray (pics "3")
rlm@216 669 blender (let [b-pics (pics "blender")]
rlm@216 670 (concat b-pics (repeat 9001 (last b-pics))))
rlm@216 671 background (repeat 9001 (File. (str base "background.png")))
rlm@216 672 targets (map
rlm@216 673 #(File. (str base "out/" (format "%07d.png" %)))
rlm@216 674 (range 0 (count main-view)))]
rlm@216 675 (dorun
rlm@216 676 (pmap
rlm@216 677 (comp
rlm@216 678 (fn [[background main-view worm-view red green blue gray blender target]]
rlm@216 679 (println target)
rlm@216 680 (sh "convert"
rlm@216 681 background
rlm@216 682 main-view "-geometry" "+18+17" "-composite"
rlm@216 683 worm-view "-geometry" "+677+17" "-composite"
rlm@216 684 green "-geometry" "+685+430" "-composite"
rlm@216 685 red "-geometry" "+788+430" "-composite"
rlm@216 686 blue "-geometry" "+894+430" "-composite"
rlm@216 687 gray "-geometry" "+1000+430" "-composite"
rlm@216 688 blender "-geometry" "+0+0" "-composite"
rlm@216 689 target))
rlm@216 690 (fn [& args] (map #(.getCanonicalPath %) args)))
rlm@216 691 background main-view worm-view red green blue gray blender targets))))
rlm@216 692 #+end_src
rlm@216 693
rlm@216 694 #+begin_src sh :results silent
rlm@216 695 cd /home/r/proj/cortex/render/worm-vision
rlm@216 696 ffmpeg -r 25 -b 9001k -i out/%07d.png -vcodec libtheora worm-vision.ogg
rlm@216 697 #+end_src
rlm@236 698
ocsenave@265 699 * Onward!
ocsenave@265 700 - As a neat bonus, this idea behind simulated vision also enables one
ocsenave@265 701 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
ocsenave@265 702 - Now that we have vision, it's time to tackle [[./hearing.org][hearing]].
ocsenave@265 703
ocsenave@265 704
ocsenave@265 705 #+appendix
ocsenave@265 706
rlm@215 707 * Headers
rlm@215 708
rlm@213 709 #+name: vision-header
rlm@213 710 #+begin_src clojure
rlm@213 711 (ns cortex.vision
rlm@213 712 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
rlm@213 713 eyes from different positions to observe the same world, and pass
rlm@213 714 the observed data to any arbitray function. Automatically reads
rlm@216 715 eye-nodes from specially prepared blender files and instantiates
rlm@213 716 them in the world as actual eyes."
rlm@213 717 {:author "Robert McIntyre"}
rlm@213 718 (:use (cortex world sense util))
rlm@213 719 (:use clojure.contrib.def)
rlm@213 720 (:import com.jme3.post.SceneProcessor)
rlm@237 721 (:import (com.jme3.util BufferUtils Screenshots))
rlm@213 722 (:import java.nio.ByteBuffer)
rlm@213 723 (:import java.awt.image.BufferedImage)
rlm@213 724 (:import (com.jme3.renderer ViewPort Camera))
rlm@216 725 (:import (com.jme3.math ColorRGBA Vector3f Matrix3f))
rlm@213 726 (:import com.jme3.renderer.Renderer)
rlm@213 727 (:import com.jme3.app.Application)
rlm@213 728 (:import com.jme3.texture.FrameBuffer)
rlm@213 729 (:import (com.jme3.scene Node Spatial)))
rlm@213 730 #+end_src
rlm@112 731
rlm@215 732 #+name: test-header
rlm@215 733 #+begin_src clojure
rlm@215 734 (ns cortex.test.vision
rlm@215 735 (:use (cortex world sense util body vision))
rlm@215 736 (:use cortex.test.body)
rlm@215 737 (:import java.awt.image.BufferedImage)
rlm@215 738 (:import javax.swing.JPanel)
rlm@215 739 (:import javax.swing.SwingUtilities)
rlm@215 740 (:import java.awt.Dimension)
rlm@215 741 (:import javax.swing.JFrame)
rlm@215 742 (:import com.jme3.math.ColorRGBA)
rlm@215 743 (:import com.jme3.scene.Node)
rlm@215 744 (:import com.jme3.math.Vector3f)
rlm@216 745 (:import java.io.File)
rlm@216 746 (:import (com.aurellem.capture Capture RatchetTimer)))
rlm@215 747 #+end_src
rlm@215 748
rlm@215 749
rlm@216 750 * Source Listing
rlm@216 751 - [[../src/cortex/vision.clj][cortex.vision]]
rlm@216 752 - [[../src/cortex/test/vision.clj][cortex.test.vision]]
rlm@216 753 - [[../src/cortex/video/magick2.clj][cortex.video.magick2]]
rlm@216 754 - [[../assets/Models/subtitles/worm-vision-subtitles.blend][worm-vision-subtitles.blend]]
rlm@216 755 #+html: <ul> <li> <a href="../org/sense.org">This org file</a> </li> </ul>
rlm@216 756 - [[http://hg.bortreb.com ][source-repository]]
rlm@216 757
rlm@35 758
rlm@24 759
rlm@212 760 * COMMENT Generate Source
rlm@34 761 #+begin_src clojure :tangle ../src/cortex/vision.clj
rlm@216 762 <<vision-header>>
rlm@216 763 <<pipeline-1>>
rlm@216 764 <<pipeline-2>>
rlm@216 765 <<retina>>
rlm@216 766 <<add-eye>>
rlm@216 767 <<sensitivity>>
rlm@216 768 <<eye-node>>
rlm@216 769 <<add-camera>>
rlm@216 770 <<kernel>>
rlm@216 771 <<main>>
rlm@216 772 <<display>>
rlm@24 773 #+end_src
rlm@24 774
rlm@68 775 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
rlm@215 776 <<test-header>>
rlm@215 777 <<test-1>>
rlm@216 778 <<test-2>>
rlm@24 779 #+end_src
rlm@216 780
rlm@216 781 #+begin_src clojure :tangle ../src/cortex/video/magick2.clj
rlm@216 782 <<magick2>>
rlm@216 783 #+end_src