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