Mercurial > cortex
view org/eyes.org @ 151:aaacf087504c
refactored vision code
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Fri, 03 Feb 2012 05:52:18 -0700 |
parents | 9d0fe7f54e14 |
children | 9e6a30b8c99a |
line wrap: on
line source
1 #+title: Simulated Sense of Sight2 #+author: Robert McIntyre3 #+email: rlm@mit.edu4 #+description: Simulated sight for AI research using JMonkeyEngine3 and clojure5 #+keywords: computer vision, jMonkeyEngine3, clojure6 #+SETUPFILE: ../../aurellem/org/setup.org7 #+INCLUDE: ../../aurellem/org/level-0.org8 #+babel: :mkdirp yes :noweb yes :exports both10 * Vision12 I want to make creatures with eyes. Each eye can be independely moved13 and should see its own version of the world depending on where it is.15 Here's how vision will work.17 Make the continuation in scene-processor take FrameBuffer,18 byte-buffer, BufferedImage already sized to the correct19 dimensions. the continuation will decide wether to "mix" them20 into the BufferedImage, lazily ignore them, or mix them halfway21 and call c/graphics card routines.23 (vision creature) will take an optional :skip argument which will24 inform the continuations in scene processor to skip the given25 number of cycles 0 means that no cycles will be skipped.27 (vision creature) will return [init-functions sensor-functions].28 The init-functions are each single-arg functions that take the29 world and register the cameras and must each be called before the30 corresponding sensor-functions. Each init-function returns the31 viewport for that eye which can be manipulated, saved, etc. Each32 sensor-function is a thunk and will return data in the same33 format as the tactile-sensor functions the structure is34 [topology, sensor-data]. Internally, these sensor-functions35 maintain a reference to sensor-data which is periodically updated36 by the continuation function established by its init-function.37 They can be queried every cycle, but their information may not38 necessairly be different every cycle.40 Each eye in the creature in blender will work the same way as41 joints -- a zero dimensional object with no geometry whose local42 coordinate system determines the orientation of the resulting43 eye. All eyes will have a parent named "eyes" just as all joints44 have a parent named "joints". The resulting camera will be a45 ChaseCamera or a CameraNode bound to the geo that is closest to46 the eye marker. The eye marker will contain the metadata for the47 eye, and will be moved by it's bound geometry. The dimensions of48 the eye's camera are equal to the dimensions of the eye's "UV"49 map.55 #+name: eyes56 #+begin_src clojure57 (ns cortex.vision58 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple59 eyes from different positions to observe the same world, and pass60 the observed data to any arbitray function."61 {:author "Robert McIntyre"}62 (:use (cortex world sense util))63 (:import com.jme3.post.SceneProcessor)64 (:import (com.jme3.util BufferUtils Screenshots))65 (:import java.nio.ByteBuffer)66 (:import java.awt.image.BufferedImage)67 (:import com.jme3.renderer.ViewPort)68 (:import com.jme3.math.ColorRGBA)69 (:import com.jme3.renderer.Renderer)70 (:import jme3tools.converters.ImageToAwt)71 (:import com.jme3.scene.Node))73 (cortex.import/mega-import-jme3)76 (defn vision-pipeline77 "Create a SceneProcessor object which wraps a vision processing78 continuation function. The continuation is a function that takes79 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],80 each of which has already been appropiately sized."81 [continuation]82 (let [byte-buffer (atom nil)83 renderer (atom nil)84 image (atom nil)]85 (proxy [SceneProcessor] []86 (initialize87 [renderManager viewPort]88 (let [cam (.getCamera viewPort)89 width (.getWidth cam)90 height (.getHeight cam)]91 (reset! renderer (.getRenderer renderManager))92 (reset! byte-buffer93 (BufferUtils/createByteBuffer94 (* width height 4)))95 (reset! image (BufferedImage.96 width height97 BufferedImage/TYPE_4BYTE_ABGR))))98 (isInitialized [] (not (nil? @byte-buffer)))99 (reshape [_ _ _])100 (preFrame [_])101 (postQueue [_])102 (postFrame103 [#^FrameBuffer fb]104 (.clear @byte-buffer)105 (continuation @renderer fb @byte-buffer @image))106 (cleanup []))))108 (defn frameBuffer->byteBuffer!109 "Transfer the data in the graphics card (Renderer, FrameBuffer) to110 the CPU (ByteBuffer)."111 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]112 (.readFrameBuffer r fb bb) bb)114 (defn byteBuffer->bufferedImage!115 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT116 style ABGR image data and place it in BufferedImage bi."117 [#^ByteBuffer bb #^BufferedImage bi]118 (Screenshots/convertScreenShot bb bi) bi)120 (defn BufferedImage!121 "Continuation which will grab the buffered image from the materials122 provided by (vision-pipeline)."123 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]124 (byteBuffer->bufferedImage!125 (frameBuffer->byteBuffer! r fb bb) bi))127 (defn add-eye128 "Add an eye to the world, calling continuation on every frame129 produced."130 [world camera continuation]131 (let [width (.getWidth camera)132 height (.getHeight camera)133 render-manager (.getRenderManager world)134 viewport (.createMainView render-manager "eye-view" camera)]135 (doto viewport136 (.setClearFlags true true true)137 (.setBackgroundColor ColorRGBA/Black)138 (.addProcessor (vision-pipeline continuation))139 (.attachScene (.getRootNode world)))))141 (defn retina-sensor-image142 "Return a map of pixel selection functions to BufferedImages143 describing the distribution of light-sensitive components on this144 geometry's surface. Each function creates an integer from the rgb145 values found in the pixel. :red, :green, :blue, :gray are already146 defined as extracting the red green blue and average components147 respectively."148 [#^Spatial eye]149 (if-let [eye-map (meta-data eye "eye")]150 (map-vals151 #(ImageToAwt/convert152 (.getImage (.loadTexture (asset-manager) %))153 false false 0)154 (eval (read-string eye-map)))))156 (defn eye-dimensions157 "returns the width and height specified in the metadata of the eye"158 [#^Spatial eye]159 (let [dimensions160 (map #(vector (.getWidth %) (.getHeight %))161 (vals (retina-sensor-image eye)))]162 [(apply max (map first dimensions))163 (apply max (map second dimensions))]))165 (defn creature-eyes166 ;;dylan167 "Return the children of the creature's \"eyes\" node."168 ;;"The eye nodes which are children of the \"eyes\" node in the169 ;;creature."170 [#^Node creature]171 (if-let [eye-node (.getChild creature "eyes")]172 (seq (.getChildren eye-node))173 (do (println-repl "could not find eyes node") [])))176 (defn attach-eye177 "Attach a Camera to the appropiate area and return the Camera."178 [#^Node creature #^Spatial eye]179 (let [target (closest-node creature eye)180 [cam-width cam-height] (eye-dimensions eye)181 cam (Camera. cam-width cam-height)]182 (.setLocation cam (.getWorldTranslation eye))183 (.setRotation cam (.getWorldRotation eye))184 (.setFrustumPerspective185 cam 45 (/ (.getWidth cam) (.getHeight cam))186 1 1000)187 (bind-sense target cam)188 cam))190 (def presets191 {:all 0xFFFFFF192 :red 0xFF0000193 :blue 0x0000FF194 :green 0x00FF00})196 (defn enable-vision197 "return [init-function sensor-functions] for a particular eye"198 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]199 (let [retinal-map (retina-sensor-image eye)200 camera (attach-eye creature eye)201 vision-image202 (atom203 (BufferedImage. (.getWidth camera)204 (.getHeight camera)205 BufferedImage/TYPE_BYTE_BINARY))]206 [(fn [world]207 (add-eye208 world camera209 (let [counter (atom 0)]210 (fn [r fb bb bi]211 (if (zero? (rem (swap! counter inc) (inc skip)))212 (reset! vision-image (BufferedImage! r fb bb bi)))))))213 (vec214 (map215 (fn [[key image]]216 (let [whites (white-coordinates image)217 topology (vec (collapse whites))218 mask (presets key)]219 (fn []220 (vector221 topology222 (vec223 (for [[x y] whites]224 (bit-and225 mask (.getRGB @vision-image x y))))))))226 retinal-map))]))228 (defn vision229 [#^Node creature & {skip :skip :or {skip 0}}]230 (reduce231 (fn [[init-a senses-a]232 [init-b senses-b]]233 [(conj init-a init-b)234 (into senses-a senses-b)])235 [[][]]236 (for [eye (creature-eyes creature)]237 (enable-vision creature eye))))240 #+end_src243 Note the use of continuation passing style for connecting the eye to a244 function to process the output. You can create any number of eyes, and245 each of them will see the world from their own =Camera=. Once every246 frame, the rendered image is copied to a =BufferedImage=, and that247 data is sent off to the continuation function. Moving the =Camera=248 which was used to create the eye will change what the eye sees.250 * Example252 #+name: test-vision253 #+begin_src clojure254 (ns cortex.test.vision255 (:use (cortex world util vision))256 (:import java.awt.image.BufferedImage)257 (:import javax.swing.JPanel)258 (:import javax.swing.SwingUtilities)259 (:import java.awt.Dimension)260 (:import javax.swing.JFrame)261 (:import com.jme3.math.ColorRGBA)262 (:import com.jme3.scene.Node)263 (:import com.jme3.math.Vector3f))265 (defn test-two-eyes266 "Testing vision:267 Tests the vision system by creating two views of the same rotating268 object from different angles and displaying both of those views in269 JFrames.271 You should see a rotating cube, and two windows,272 each displaying a different view of the cube."273 []274 (let [candy275 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]276 (world277 (doto (Node.)278 (.attachChild candy))279 {}280 (fn [world]281 (let [cam (.clone (.getCamera world))282 width (.getWidth cam)283 height (.getHeight cam)]284 (add-eye world cam285 ;;no-op286 (comp (view-image) BufferedImage!)287 )288 (add-eye world289 (doto (.clone cam)290 (.setLocation (Vector3f. -10 0 0))291 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))292 ;;no-op293 (comp (view-image) BufferedImage!))294 ;; This is here to restore the main view295 ;; after the other views have completed processing296 (add-eye world (.getCamera world) no-op)))297 (fn [world tpf]298 (.rotate candy (* tpf 0.2) 0 0)))))299 #+end_src301 #+results: test-vision302 : #'cortex.test.vision/test-two-eyes304 The example code will create two videos of the same rotating object305 from different angles. It can be used both for stereoscopic vision306 simulation or for simulating multiple creatures, each with their own307 sense of vision.309 - As a neat bonus, this idea behind simulated vision also enables one310 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].313 * COMMENT code generation314 #+begin_src clojure :tangle ../src/cortex/vision.clj315 <<eyes>>316 #+end_src318 #+begin_src clojure :tangle ../src/cortex/test/vision.clj319 <<test-vision>>320 #+end_src