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
|
rlm@34
|
10 * Vision
|
rlm@23
|
11
|
rlm@34
|
12 I want to make creatures with eyes. Each eye can be independely moved
|
rlm@34
|
13 and should see its own version of the world depending on where it is.
|
rlm@66
|
14 #+name: eyes
|
rlm@23
|
15 #+begin_src clojure
|
rlm@34
|
16 (ns cortex.vision
|
rlm@34
|
17 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
|
rlm@34
|
18 eyes from different positions to observe the same world, and pass
|
rlm@34
|
19 the observed data to any arbitray function."
|
rlm@34
|
20 {:author "Robert McIntyre"}
|
rlm@34
|
21 (:use cortex.world)
|
rlm@34
|
22 (:import com.jme3.post.SceneProcessor)
|
rlm@113
|
23 (:import (com.jme3.util BufferUtils Screenshots))
|
rlm@34
|
24 (:import java.nio.ByteBuffer)
|
rlm@34
|
25 (:import java.awt.image.BufferedImage)
|
rlm@34
|
26 (:import com.jme3.renderer.ViewPort)
|
rlm@113
|
27 (:import com.jme3.math.ColorRGBA)
|
rlm@113
|
28 (:import com.jme3.renderer.Renderer))
|
rlm@23
|
29
|
rlm@113
|
30
|
rlm@113
|
31 (defn vision-pipeline
|
rlm@34
|
32 "Create a SceneProcessor object which wraps a vision processing
|
rlm@113
|
33 continuation function. The continuation is a function that takes
|
rlm@113
|
34 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],
|
rlm@113
|
35 each of which has already been appropiately sized."
|
rlm@23
|
36 [continuation]
|
rlm@23
|
37 (let [byte-buffer (atom nil)
|
rlm@113
|
38 renderer (atom nil)
|
rlm@113
|
39 image (atom nil)]
|
rlm@23
|
40 (proxy [SceneProcessor] []
|
rlm@23
|
41 (initialize
|
rlm@23
|
42 [renderManager viewPort]
|
rlm@23
|
43 (let [cam (.getCamera viewPort)
|
rlm@23
|
44 width (.getWidth cam)
|
rlm@23
|
45 height (.getHeight cam)]
|
rlm@23
|
46 (reset! renderer (.getRenderer renderManager))
|
rlm@23
|
47 (reset! byte-buffer
|
rlm@23
|
48 (BufferUtils/createByteBuffer
|
rlm@113
|
49 (* width height 4)))
|
rlm@113
|
50 (reset! image (BufferedImage.
|
rlm@113
|
51 width height
|
rlm@113
|
52 BufferedImage/TYPE_4BYTE_ABGR))))
|
rlm@23
|
53 (isInitialized [] (not (nil? @byte-buffer)))
|
rlm@23
|
54 (reshape [_ _ _])
|
rlm@23
|
55 (preFrame [_])
|
rlm@23
|
56 (postQueue [_])
|
rlm@23
|
57 (postFrame
|
rlm@23
|
58 [#^FrameBuffer fb]
|
rlm@23
|
59 (.clear @byte-buffer)
|
rlm@113
|
60 (continuation @renderer fb @byte-buffer @image))
|
rlm@23
|
61 (cleanup []))))
|
rlm@23
|
62
|
rlm@113
|
63 (defn frameBuffer->byteBuffer!
|
rlm@113
|
64 "Transfer the data in the graphics card (Renderer, FrameBuffer) to
|
rlm@113
|
65 the CPU (ByteBuffer)."
|
rlm@113
|
66 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]
|
rlm@113
|
67 (.readFrameBuffer r fb bb) bb)
|
rlm@113
|
68
|
rlm@113
|
69 (defn byteBuffer->bufferedImage!
|
rlm@113
|
70 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT
|
rlm@113
|
71 style ABGR image data and place it in BufferedImage bi."
|
rlm@113
|
72 [#^ByteBuffer bb #^BufferedImage bi]
|
rlm@113
|
73 (Screenshots/convertScreenShot bb bi) bi)
|
rlm@113
|
74
|
rlm@113
|
75 (defn BufferedImage!
|
rlm@113
|
76 "Continuation which will grab the buffered image from the materials
|
rlm@113
|
77 provided by (vision-pipeline)."
|
rlm@113
|
78 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]
|
rlm@113
|
79 (byteBuffer->bufferedImage!
|
rlm@113
|
80 (frameBuffer->byteBuffer! r fb bb) bi))
|
rlm@112
|
81
|
rlm@23
|
82 (defn add-eye
|
rlm@34
|
83 "Add an eye to the world, calling continuation on every frame
|
rlm@34
|
84 produced."
|
rlm@23
|
85 [world camera continuation]
|
rlm@23
|
86 (let [width (.getWidth camera)
|
rlm@23
|
87 height (.getHeight camera)
|
rlm@23
|
88 render-manager (.getRenderManager world)
|
rlm@23
|
89 viewport (.createMainView render-manager "eye-view" camera)]
|
rlm@23
|
90 (doto viewport
|
rlm@23
|
91 (.setClearFlags true true true)
|
rlm@112
|
92 (.setBackgroundColor ColorRGBA/Black)
|
rlm@113
|
93 (.addProcessor (vision-pipeline continuation))
|
rlm@23
|
94 (.attachScene (.getRootNode world)))))
|
rlm@34
|
95 #+end_src
|
rlm@23
|
96
|
rlm@112
|
97 #+results: eyes
|
rlm@112
|
98 : #'cortex.vision/add-eye
|
rlm@112
|
99
|
rlm@34
|
100 Note the use of continuation passing style for connecting the eye to a
|
rlm@34
|
101 function to process the output. You can create any number of eyes, and
|
rlm@34
|
102 each of them will see the world from their own =Camera=. Once every
|
rlm@34
|
103 frame, the rendered image is copied to a =BufferedImage=, and that
|
rlm@34
|
104 data is sent off to the continuation function. Moving the =Camera=
|
rlm@34
|
105 which was used to create the eye will change what the eye sees.
|
rlm@23
|
106
|
rlm@34
|
107 * Example
|
rlm@23
|
108
|
rlm@66
|
109 #+name: test-vision
|
rlm@23
|
110 #+begin_src clojure
|
rlm@68
|
111 (ns cortex.test.vision
|
rlm@34
|
112 (:use (cortex world util vision))
|
rlm@34
|
113 (:import java.awt.image.BufferedImage)
|
rlm@34
|
114 (:import javax.swing.JPanel)
|
rlm@34
|
115 (:import javax.swing.SwingUtilities)
|
rlm@34
|
116 (:import java.awt.Dimension)
|
rlm@34
|
117 (:import javax.swing.JFrame)
|
rlm@34
|
118 (:import com.jme3.math.ColorRGBA)
|
rlm@45
|
119 (:import com.jme3.scene.Node)
|
rlm@113
|
120 (:import com.jme3.math.Vector3f))
|
rlm@23
|
121
|
rlm@34
|
122 (defn view-image
|
rlm@99
|
123 "Initailizes a JPanel on which you may draw a BufferedImage.
|
rlm@99
|
124 Returns a function that accepts a BufferedImage and draws it to the
|
rlm@99
|
125 JPanel."
|
rlm@99
|
126 []
|
rlm@34
|
127 (let [image
|
rlm@34
|
128 (atom
|
rlm@99
|
129 (BufferedImage. 1 1 BufferedImage/TYPE_4BYTE_ABGR))
|
rlm@34
|
130 panel
|
rlm@34
|
131 (proxy [JPanel] []
|
rlm@34
|
132 (paint
|
rlm@34
|
133 [graphics]
|
rlm@34
|
134 (proxy-super paintComponent graphics)
|
rlm@99
|
135 (.drawImage graphics @image 0 0 nil)))
|
rlm@99
|
136 frame (JFrame. "Display Image")]
|
rlm@34
|
137 (SwingUtilities/invokeLater
|
rlm@34
|
138 (fn []
|
rlm@99
|
139 (doto frame
|
rlm@34
|
140 (-> (.getContentPane) (.add panel))
|
rlm@34
|
141 (.pack)
|
rlm@34
|
142 (.setLocationRelativeTo nil)
|
rlm@99
|
143 (.setResizable true)
|
rlm@34
|
144 (.setVisible true))))
|
rlm@34
|
145 (fn [#^BufferedImage i]
|
rlm@34
|
146 (reset! image i)
|
rlm@99
|
147 (.setSize frame (+ 8 (.getWidth i)) (+ 28 (.getHeight i)))
|
rlm@99
|
148 (.repaint panel 0 0 (.getWidth i) (.getHeight i)))))
|
rlm@23
|
149
|
rlm@36
|
150 (defn test-two-eyes
|
rlm@69
|
151 "Testing vision:
|
rlm@69
|
152 Tests the vision system by creating two views of the same rotating
|
rlm@69
|
153 object from different angles and displaying both of those views in
|
rlm@69
|
154 JFrames.
|
rlm@69
|
155
|
rlm@69
|
156 You should see a rotating cube, and two windows,
|
rlm@69
|
157 each displaying a different view of the cube."
|
rlm@36
|
158 []
|
rlm@58
|
159 (let [candy
|
rlm@58
|
160 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]
|
rlm@112
|
161 (world
|
rlm@112
|
162 (doto (Node.)
|
rlm@112
|
163 (.attachChild candy))
|
rlm@112
|
164 {}
|
rlm@112
|
165 (fn [world]
|
rlm@112
|
166 (let [cam (.clone (.getCamera world))
|
rlm@112
|
167 width (.getWidth cam)
|
rlm@112
|
168 height (.getHeight cam)]
|
rlm@112
|
169 (add-eye world cam
|
rlm@113
|
170 ;;no-op
|
rlm@113
|
171 (comp (view-image) BufferedImage!)
|
rlm@112
|
172 )
|
rlm@112
|
173 (add-eye world
|
rlm@112
|
174 (doto (.clone cam)
|
rlm@112
|
175 (.setLocation (Vector3f. -10 0 0))
|
rlm@112
|
176 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
|
rlm@113
|
177 ;;no-op
|
rlm@113
|
178 (comp (view-image) BufferedImage!))
|
rlm@112
|
179 ;; This is here to restore the main view
|
rlm@112
|
180 ;; after the other views have completed processing
|
rlm@112
|
181 (add-eye world (.getCamera world) no-op)))
|
rlm@112
|
182 (fn [world tpf]
|
rlm@112
|
183 (.rotate candy (* tpf 0.2) 0 0)))))
|
rlm@23
|
184 #+end_src
|
rlm@23
|
185
|
rlm@112
|
186 #+results: test-vision
|
rlm@112
|
187 : #'cortex.test.vision/test-two-eyes
|
rlm@112
|
188
|
rlm@34
|
189 The example code will create two videos of the same rotating object
|
rlm@34
|
190 from different angles. It can be used both for stereoscopic vision
|
rlm@34
|
191 simulation or for simulating multiple creatures, each with their own
|
rlm@34
|
192 sense of vision.
|
rlm@24
|
193
|
rlm@35
|
194 - As a neat bonus, this idea behind simulated vision also enables one
|
rlm@35
|
195 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
|
rlm@35
|
196
|
rlm@24
|
197
|
rlm@24
|
198 * COMMENT code generation
|
rlm@34
|
199 #+begin_src clojure :tangle ../src/cortex/vision.clj
|
rlm@24
|
200 <<eyes>>
|
rlm@24
|
201 #+end_src
|
rlm@24
|
202
|
rlm@68
|
203 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
|
rlm@24
|
204 <<test-vision>>
|
rlm@24
|
205 #+end_src
|