├── .gitignore ├── LICENSE ├── README.md ├── ex2_events_visualization.py ├── ex2_events_viz.ipynb ├── images ├── balance_polarities_gray.png ├── balance_polarities_red_blue.png ├── hist2d_neg_veridi.png ├── hist2d_pos_veridi.png ├── histogram_smoothing.png ├── movie_vlc_pointset.png ├── space_time_pol.png ├── space_time_pol_edge_on.png ├── t_ave_both_pols.png ├── t_ave_neg.png ├── t_ave_pos.png ├── ternary_gray.png ├── ternary_red_blue.png ├── ts_exp_balance_pol_red_blue.png ├── ts_exp_neg.png ├── ts_exp_pol.png ├── ts_exp_pol_red_blue.png ├── ts_exp_pos.png ├── voxel_linvote.png ├── voxel_linvote_pol.png └── voxel_nn.png ├── movie.mp4 ├── print_seismic_cmap.py └── slider_depth └── events_chunk.txt /.gitignore: -------------------------------------------------------------------------------- 1 | tempDir/ 2 | zips/ 3 | .ipynb_checkpoints/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TU Berlin - Robotic Interactive Perception 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visualization of events using point clouds, event frames, voxel grids, etc. 2 | 3 | **Event-based Robot Vision. Exercise 2** 4 | 5 | ### Statement of the exercise 6 | 7 | See the [jupyter notebook](ex2_events_viz.ipynb). It is best visualized locally, as the images may not show online. In a terminal, run the command: 8 | 9 | ~/.local/bin/jupyter-notebook ex2_events_viz.ipynb 10 | 11 | ### Solution 12 | 13 | For the solution, see the python code (2.7) in the file [ex2_events_visualization.py](ex2_events_visualization.py) 14 | 15 | You may run it in a terminal, using: 16 | 17 | python ex2_events_visualization.py 18 | 19 | Alternatively, you may run the code cell-by-cell using [Spyder - The Scientific Python Development Environment](https://www.spyder-ide.org/) 20 | 21 | spyder ex2_events_visualization.py 22 | 23 | Press (Shift + Enter) to run the current code cell (delimited by characters `# %%`) and advance to the next one. 24 | -------------------------------------------------------------------------------- /ex2_events_visualization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Apr 25 19:57:35 2020 4 | 5 | Read and write event data. 6 | 7 | Conversion of event data into frames (images, 2D): 8 | - histograms of events 9 | - thresholded (1 f-stop) 10 | - brightness increment images 11 | - time surfaces: exponential decay or average time 12 | With polarity on the same representation or split by polarity 13 | 14 | Visualization of event data in 3D 15 | - with or without polarity 16 | - change of viewpoint and movie creation 17 | 18 | Write it nicely in utility functions 19 | 20 | @author: ggb 21 | """ 22 | 23 | import os 24 | import numpy as np 25 | from matplotlib import pyplot as plt 26 | 27 | # This import registers the 3D projection, but is otherwise unused. 28 | from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import 29 | 30 | plt.close('all') 31 | 32 | 33 | # %% Read a file of events and write another file with a subset of them 34 | filename_sub = 'slider_depth/events_chunk.txt' 35 | """ 36 | # This is how the file events_chunk.txt was generated from the events.txt file in the IJRR 2017 dataset 37 | events_raw = open('slider_depth/events.txt', "r") 38 | events_sub = open(filename_sub, "w") 39 | # format: timestamp, x, y, polarity 40 | 41 | for k in range(50000): 42 | line = events_raw.readline() 43 | #print(line) 44 | events_sub.write(line) 45 | 46 | events_raw.close() 47 | events_sub.close() 48 | """ 49 | 50 | 51 | # %% Read file with a subset of events 52 | # Simple. There may be more efficient ways. 53 | def extract_data(filename): 54 | infile = open(filename, 'r') 55 | timestamp = [] 56 | x = [] 57 | y = [] 58 | pol = [] 59 | for line in infile: 60 | words = line.split() 61 | timestamp.append(float(words[0])) 62 | x.append(int(words[1])) 63 | y.append(int(words[2])) 64 | pol.append(int(words[3])) 65 | infile.close() 66 | return timestamp,x,y,pol 67 | 68 | # Call the function to read data 69 | timestamp, x, y, pol = extract_data(filename_sub) 70 | 71 | 72 | # %% Sensor size 73 | 74 | # Get the size of the sensor using a grayscale frame (in case of a DAVIS) 75 | # filename_frame = 'slider_depth/images/frame_00000000.png' 76 | # import cv2 77 | # img = cv2.imread(filename_frame, cv2.IMREAD_GRAYSCALE) 78 | # print img.shape 79 | # img = np.zeros(img.shape, np.int) 80 | 81 | # For this exercise, we just provide the sensor size (height, width) 82 | img_size = (180,240) 83 | 84 | 85 | # %% Brightness incremet image (Balance of event polarities) 86 | num_events = 5000 # Number of events used 87 | print("Brightness incremet image: numevents = ", num_events) 88 | 89 | # Compute image by accumulating polarities. 90 | img = np.zeros(img_size, np.int) 91 | for i in range(num_events): 92 | # Need to convert the polarity bit from {0,1} to {-1,+1} and accumulate 93 | img[y[i],x[i]] += (2*pol[i]-1) 94 | 95 | # Display the image in grayscale 96 | fig = plt.figure() 97 | fig.suptitle('Balance of event polarities') 98 | maxabsval = np.amax(np.abs(img)) 99 | plt.imshow(img, cmap='gray', clim=(-maxabsval,maxabsval)) 100 | plt.xlabel("x [pixels]") 101 | plt.ylabel("y [pixels]") 102 | plt.colorbar() 103 | plt.show() 104 | 105 | # Same plot as above, but changing the color map 106 | fig = plt.figure() 107 | fig.suptitle('Balance of event polarities') 108 | maxabsval = np.amax(np.abs(img)) 109 | plt.imshow(img, cmap='seismic_r', clim=(-maxabsval,maxabsval)) 110 | plt.xlabel("x [pixels]") 111 | plt.ylabel("y [pixels]") 112 | plt.colorbar() 113 | plt.show() 114 | 115 | 116 | # %% 2D Histograms of events, split by polarity (positive and negative events in separate images) 117 | img_pos = np.zeros(img_size, np.int) 118 | img_neg = np.zeros(img_size, np.int) 119 | for i in range(num_events): 120 | if (pol[i] > 0): 121 | img_pos[y[i],x[i]] += 1 # count events 122 | else: 123 | img_neg[y[i],x[i]] += 1 124 | 125 | fig = plt.figure() 126 | fig.suptitle('Histogram of positive events') 127 | plt.imshow(img_pos) 128 | plt.xlabel("x [pixels]") 129 | plt.ylabel("y [pixels]") 130 | plt.colorbar() 131 | plt.show() 132 | 133 | fig = plt.figure() 134 | fig.suptitle('Histogram of negative events') 135 | plt.imshow(img_neg) 136 | plt.xlabel("x [pixels]") 137 | plt.ylabel("y [pixels]") 138 | plt.colorbar() 139 | plt.show() 140 | 141 | 142 | # %% Thresholded brightness increment image (Ternary image) 143 | 144 | # What if we only use 3 values in the event accumulation image? 145 | # Saturated signal: -1, 0, 1 146 | # For example, store the polarity of the last event at each pixel 147 | img = np.zeros(img_size, np.int) 148 | for i in range(num_events): 149 | img[y[i],x[i]] = (2*pol[i]-1) # no accumulation; overwrite the stored value 150 | 151 | # Display the ternary image 152 | fig = plt.figure() 153 | fig.suptitle('Last event polarity per pixel') 154 | plt.imshow(img, cmap='gray') 155 | #plt.imshow(img, cmap='bwr') 156 | plt.xlabel("x [pixels]") 157 | plt.ylabel("y [pixels]") 158 | plt.colorbar() 159 | plt.show() 160 | 161 | 162 | 163 | 164 | # _____________________________________________________________________________ 165 | # %% Time surface (or time map, or SAE="Surface of Active Events") 166 | num_events = len(timestamp) 167 | print("Time surface: numevents = ", num_events) 168 | 169 | img = np.zeros(img_size, np.float32) 170 | t_ref = timestamp[-1] # time of the last event in the packet 171 | tau = 0.03 # decay parameter (in seconds) 172 | for i in range(num_events): 173 | img[y[i],x[i]] = np.exp(-(t_ref-timestamp[i]) / tau) 174 | 175 | fig = plt.figure() 176 | fig.suptitle('Time surface (exp decay). Both polarities') 177 | plt.imshow(img) 178 | plt.xlabel("x [pixels]") 179 | plt.ylabel("y [pixels]") 180 | plt.colorbar() 181 | plt.show() 182 | 183 | 184 | # %% Time surface (or time map, or SAE), separated by polarity 185 | sae_pos = np.zeros(img_size, np.float32) 186 | sae_neg = np.zeros(img_size, np.float32) 187 | for i in range(num_events): 188 | if (pol[i] > 0): 189 | sae_pos[y[i],x[i]] = np.exp(-(t_ref-timestamp[i]) / tau) 190 | else: 191 | sae_neg[y[i],x[i]] = np.exp(-(t_ref-timestamp[i]) / tau) 192 | 193 | fig = plt.figure() 194 | fig.suptitle('Time surface (exp decay) of positive events') 195 | plt.imshow(sae_pos) 196 | plt.xlabel("x [pixels]") 197 | plt.ylabel("y [pixels]") 198 | plt.colorbar() 199 | plt.show() 200 | 201 | fig = plt.figure() 202 | fig.suptitle('Time surface (exp decay) of negative events') 203 | plt.imshow(sae_neg) 204 | plt.xlabel("x [pixels]") 205 | plt.ylabel("y [pixels]") 206 | plt.colorbar() 207 | plt.show() 208 | 209 | 210 | # %% Time surface (or time map, or SAE), using polarity as sign of the time map 211 | sae = np.zeros(img_size, np.float32) 212 | for i in range(num_events): 213 | if (pol[i] > 0): 214 | sae[y[i],x[i]] = np.exp(-(t_ref-timestamp[i]) / tau) 215 | else: 216 | sae[y[i],x[i]] = -np.exp(-(t_ref-timestamp[i]) / tau) 217 | 218 | fig = plt.figure() 219 | fig.suptitle('Time surface (exp decay), using polarity as sign') 220 | plt.imshow(sae, cmap='seismic') # using color (Red/blue) 221 | plt.xlabel("x [pixels]") 222 | plt.ylabel("y [pixels]") 223 | plt.colorbar() 224 | plt.show() 225 | 226 | 227 | # %% "Balance of time surfaces" 228 | # Accumulate exponential decays using polarity as sign of the time map 229 | sae = np.zeros(img_size, np.float32) 230 | for i in range(num_events): 231 | if (pol[i] > 0): 232 | sae[y[i],x[i]] += np.exp(-(t_ref-timestamp[i]) / tau) 233 | else: 234 | sae[y[i],x[i]] -= np.exp(-(t_ref-timestamp[i]) / tau) 235 | 236 | fig = plt.figure() 237 | fig.suptitle('Time surface (exp decay), balance of both polarities') 238 | #plt.imshow(sae) 239 | maxabsval = np.amax(np.abs(sae)) 240 | plt.imshow(sae, cmap='seismic', clim=(-maxabsval,maxabsval)) 241 | plt.xlabel("x [pixels]") 242 | plt.ylabel("y [pixels]") 243 | plt.colorbar() 244 | plt.show() 245 | 246 | 247 | # %% Average timestamp per pixel 248 | sae = np.zeros(img_size, np.float32) 249 | count = np.zeros(img_size, np.int) 250 | for i in range(num_events): 251 | sae[y[i],x[i]] += timestamp[i] 252 | count[y[i],x[i]] += 1 253 | 254 | # Compute per-pixel average if count at the pixel is >1 255 | count [count < 1] = 1 # to avoid division by zero 256 | sae = sae / count 257 | 258 | fig = plt.figure() 259 | fig.suptitle('Average timestamps regardless of polarity') 260 | plt.imshow(sae) 261 | plt.xlabel("x [pixels]") 262 | plt.ylabel("y [pixels]") 263 | plt.colorbar() 264 | plt.show() 265 | 266 | 267 | # %% Average timestamp per pixel. Separate by polarity 268 | sae_pos = np.zeros(img_size, np.float32) 269 | sae_neg = np.zeros(img_size, np.float32) 270 | count_pos = np.zeros(img_size, np.int) 271 | count_neg = np.zeros(img_size, np.int) 272 | for i in range(num_events): 273 | if (pol[i] > 0): 274 | sae_pos[y[i],x[i]] += timestamp[i] 275 | count_pos[y[i],x[i]] += 1 276 | else: 277 | sae_neg[y[i],x[i]] += timestamp[i] 278 | count_neg[y[i],x[i]] += 1 279 | 280 | # Compute per-pixel average if count at the pixel is >1 281 | count_pos [count_pos < 1] = 1; sae_pos = sae_pos / count_pos 282 | count_neg [count_neg < 1] = 1; sae_neg = sae_neg / count_neg 283 | 284 | fig = plt.figure() 285 | fig.suptitle('Average timestamps of positive events') 286 | plt.imshow(sae_pos) 287 | plt.xlabel("x [pixels]") 288 | plt.ylabel("y [pixels]") 289 | plt.colorbar() 290 | plt.show() 291 | 292 | fig = plt.figure() 293 | fig.suptitle('Average timestamps of negative events') 294 | plt.imshow(sae_neg) 295 | plt.xlabel("x [pixels]") 296 | plt.ylabel("y [pixels]") 297 | plt.colorbar() 298 | plt.show() 299 | 300 | 301 | 302 | 303 | # _____________________________________________________________________________ 304 | # %% 3D plot 305 | # Time axis in horizontal position 306 | 307 | m = 2000 # Number of points to plot 308 | print("Space-time plot and movie: numevents = ", m) 309 | 310 | # Plot without polarity 311 | fig = plt.figure() 312 | ax = fig.add_subplot(111, projection='3d') 313 | #ax.set_aspect('equal') # only works for time in Z axis 314 | ax.scatter(x[:m], timestamp[:m], y[:m], marker='.', c='b') 315 | ax.set_xlabel('x [pix]') 316 | ax.set_ylabel('time [s]') 317 | ax.set_zlabel('y [pix] ') 318 | ax.view_init(azim=-90, elev=-180) # Change viewpoint with the mouse, for example 319 | plt.show() 320 | 321 | 322 | # %% Plot each polarity with a different color (red / blue) 323 | idx_pos = np.asarray(pol[:m]) > 0 324 | idx_neg = np.logical_not(idx_pos) 325 | xnp = np.asarray(x[:m]) 326 | ynp = np.asarray(y[:m]) 327 | tnp = np.asarray(timestamp[:m]) 328 | fig = plt.figure() 329 | ax = fig.add_subplot(111, projection='3d') 330 | ax.scatter(xnp[idx_pos], tnp[idx_pos], ynp[idx_pos], marker='.', c='b') 331 | ax.scatter(xnp[idx_neg], tnp[idx_neg], ynp[idx_neg], marker='.', c='r') 332 | ax.set(xlabel='x [pix]', ylabel='time [s]', zlabel='y [pix]') 333 | ax.view_init(azim=-90, elev=-180) 334 | plt.show() 335 | 336 | 337 | # %% Transition between two viewpoints 338 | num_interp_viewpoints = 60 # number of interpolated viewpoints 339 | ele = np.linspace(-150,-180, num=num_interp_viewpoints) 340 | azi = np.linspace( -50, -90, num=num_interp_viewpoints) 341 | 342 | # Create directory to save images and then create a movie 343 | dirName = 'tempDir' 344 | if not os.path.exists(dirName): 345 | os.mkdir(dirName) 346 | print("Directory " , dirName , " Created ") 347 | else: 348 | print("Directory " , dirName , " already exists") 349 | 350 | for ii in xrange(0,num_interp_viewpoints): 351 | ax.view_init(azim=azi[ii], elev=ele[ii]) 352 | plt.savefig(dirName + "/movie%04d.png" % ii) 353 | 354 | # %% Create a movie using ffmpeg static build (https://johnvansickle.com/ffmpeg/) 355 | # Video coding options, such as lossless: https://trac.ffmpeg.org/wiki/Encode/H.264 356 | def createMovie(): 357 | os.system("/home/ggb/Downloads/ffmpeg-4.2.2-i686-static/ffmpeg -r 20 -i " 358 | + dirName + "/movie%04d.png -c:v libx264 -crf 0 -y movie_new.mp4") 359 | 360 | # Call the function to create the movie 361 | createMovie() 362 | 363 | 364 | 365 | 366 | # _____________________________________________________________________________ 367 | # %% Voxel grid 368 | 369 | num_bins = 5 370 | print("Number of time bins = ", num_bins) 371 | 372 | t_max = np.amax(np.asarray(timestamp[:m])) 373 | t_min = np.amin(np.asarray(timestamp[:m])) 374 | t_range = t_max - t_min 375 | dt_bin = t_range / num_bins # size of the time bins (bins) 376 | t_edges = np.linspace(t_min,t_max,num_bins+1) # Boundaries of the bins 377 | 378 | # Compute 3D histogram of events manually with a loop 379 | # ("Zero-th order or nearest neighbor voting") 380 | hist3d = np.zeros(img.shape+(num_bins,), np.int) 381 | for ii in xrange(m): 382 | idx_t = int( (timestamp[ii]-t_min) / dt_bin ) 383 | if idx_t >= num_bins: 384 | idx_t = num_bins-1 # only one element (the last one) 385 | hist3d[y[ii],x[ii],idx_t] += 1 386 | 387 | # Checks: 388 | print("hist3d") 389 | print hist3d.shape 390 | print np.sum(hist3d) # This should equal the number of votes 391 | 392 | 393 | # %% Compute 3D histogram of events using numpy function histogramdd 394 | # https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogramdd.html#numpy.histogramdd 395 | 396 | # Specify bin edges in each dimension 397 | bin_edges = (np.linspace(0,img_size[0],img_size[0]+1), 398 | np.linspace(0,img_size[1],img_size[1]+1), t_edges) 399 | yxt = np.transpose(np.array([y[:m], x[:m], timestamp[:m]])) 400 | hist3dd, edges = np.histogramdd(yxt, bins=bin_edges) 401 | 402 | # Checks 403 | print("\nhist3dd") 404 | print("min = ", np.min(hist3dd)) 405 | print("max = ", np.max(hist3dd)) 406 | print np.sum(hist3dd) 407 | print np.linalg.norm( hist3dd - hist3d) # Check: zero if both histograms are equal 408 | print("Ratio of occupied bins = ", np.sum(hist3dd > 0) / float(np.prod(hist3dd.shape)) ) 409 | 410 | 411 | # Plot of the 3D histogram. Empty cells are transparent (not displayed) 412 | # Example: https://matplotlib.org/3.1.1/gallery/mplot3d/voxels_rgb.html#sphx-glr-gallery-mplot3d-voxels-rgb-py 413 | 414 | fig = plt.figure() 415 | fig.suptitle('3D histogram (voxel grid), zero-th order voting') 416 | ax = fig.gca(projection='3d') 417 | # prepare some coordinates 418 | r, g, b = np.indices((img_size[0]+1,img_size[1]+1,num_bins+1)) 419 | ax.voxels(g,b,r, hist3d) # No need to swap the data to plot with reordered axes 420 | ax.set(xlabel='x', ylabel='time bin', zlabel='y') 421 | ax.view_init(azim=-90, elev=-180) # edge-on, along time axis 422 | #ax.view_init(azim=-63, elev=-145) # oblique viewpoint 423 | plt.show() 424 | 425 | 426 | # %% Compute interpolated 3D histogram (voxel grid) 427 | hist3d_interp = np.zeros(img.shape+(num_bins,), np.float64) 428 | for ii in xrange(m-1): 429 | tn = (timestamp[ii] - t_min) / dt_bin # normalized time, in [0,num_bins] 430 | ti = np.floor(tn-0.5) # index of the left bin 431 | dt = (tn-0.5) - ti # delta fraction 432 | # Voting on two adjacent bins 433 | if ti >=0 : 434 | hist3d_interp[y[ii],x[ii],int(ti) ] += 1. - dt 435 | if ti < num_bins-1 : 436 | hist3d_interp[y[ii],x[ii],int(ti)+1] += dt 437 | 438 | # Checks 439 | print("\nhist3d_interp") 440 | print("min = ", np.min(hist3d_interp)) 441 | print("max = ", np.max(hist3d_interp)) 442 | print np.sum(hist3d_interp) 443 | # Some votes are lost because of the missing last layer 444 | print np.linalg.norm( hist3d - hist3d_interp) 445 | print("Ratio of occupied bins = ", np.sum(hist3d_interp > 0) / float(np.prod(hist3d_interp.shape)) ) 446 | 447 | # Plot voxel grid 448 | colors = np.zeros(hist3d_interp.shape + (3,)) 449 | tmp = hist3d_interp/np.amax(hist3d_interp) # normalize in [0,1] 450 | colors[..., 0] = tmp 451 | colors[..., 1] = tmp 452 | colors[..., 2] = tmp 453 | 454 | fig = plt.figure() 455 | fig.suptitle('Interpolated 3D histogram (voxel grid)') 456 | ax = fig.gca(projection='3d') 457 | ax.voxels(g,b,r, hist3d_interp, facecolors=colors) 458 | ax.set(xlabel='x', ylabel='time bin', zlabel='y') 459 | ax.view_init(azim=-63, elev=-145) 460 | plt.show() 461 | 462 | # %% A different visualization viewpoint 463 | ax.view_init(azim=-90, elev=-180) # edge-on, along time axis 464 | plt.show() 465 | 466 | 467 | # %% Compute interpolated 3D histogram (voxel grid) using polarity 468 | hist3d_interp_pol = np.zeros(img.shape+(num_bins,), np.float64) 469 | for ii in xrange(m-1): 470 | tn = (timestamp[ii] - t_min) / dt_bin # normalized time, in [0,num_bins] 471 | ti = np.floor(tn-0.5) # index of the left bin 472 | dt = (tn-0.5) - ti # delta fraction 473 | # Voting on two adjacent bins 474 | if ti >=0 : 475 | hist3d_interp_pol[y[ii],x[ii],int(ti) ] += (1. - dt) * (2*pol[ii]-1) 476 | if ti < num_bins-1 : 477 | hist3d_interp_pol[y[ii],x[ii],int(ti)+1] += dt * (2*pol[ii]-1) 478 | 479 | # Checks 480 | # Some votes are lost because of the missing last layer 481 | print("\nhist3d_interp_pol") 482 | print("min = ", np.min(hist3d_interp_pol)) 483 | print("max = ", np.max(hist3d_interp_pol)) 484 | print np.sum(np.abs(hist3d_interp_pol)) 485 | print("Ratio of occupied bins = ", np.sum(np.abs(hist3d_interp_pol) > 0) / float(np.prod(hist3d_interp_pol.shape)) ) 486 | 487 | # Plot interpolated voxel grid using polarity 488 | # Normalize the symmetric range to [0,1] 489 | maxabsval = np.amax(np.abs(hist3d_interp_pol)) 490 | colors = np.zeros(hist3d_interp_pol.shape + (3,)) 491 | tmp = (hist3d_interp_pol + maxabsval)/(2*maxabsval) 492 | colors[..., 0] = tmp 493 | colors[..., 1] = tmp 494 | colors[..., 2] = tmp 495 | 496 | fig = plt.figure() 497 | fig.suptitle('Interpolated 3D histogram (voxel grid), including polarity') 498 | ax = fig.gca(projection='3d') 499 | ax.voxels(g,b,r, hist3d_interp_pol, facecolors=colors) 500 | ax.set(xlabel='x', ylabel='time bin', zlabel='y') 501 | ax.view_init(azim=-63, elev=-145) 502 | plt.show() 503 | 504 | # %% Better visualization viewpoint to see positive and negative edges 505 | ax.view_init(azim=-90, elev=-180) # edge-on, along time axis 506 | plt.show() 507 | -------------------------------------------------------------------------------- /ex2_events_viz.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Event-data Representation and Visualization" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "The **goals** of this exercise are:\n", 15 | "- to familiarize yourself with the data produced by an event-based camera\n", 16 | "- to practice converting the event data into image-like (also called \"grid-based\" or \"array-based\") representations, which is a common practice of many algorithms (without judging whether it is \"optimal\" or not).\n", 17 | "- to produce \"pretty plots\" (never underestimate the value of good figures on reports and papers).\n", 18 | "\n", 19 | "For this exercise let us use data from the `slider_depth` sequence in the [Event Camera Dataset and Simulator (IJRR'17)](http://rpg.ifi.uzh.ch/davis_data.html). A small portion of this data is provided in the folder `/slider_depth`\". The reasons to use this file are that it is available in simple txt format and that it is small (i.e., manageable). \n", 20 | "\n", 21 | "The sequences in the above-mentioned dataset were recorded with a DAVIS240C camera (from iniVation) and they also contain grayscale frames, recorded at about 25 Hz (frames per second). The frames can be helpful to get a better idea of the scene. Once you are comfortable with the exercise, feel free to use other sequences or data from other publicly available [datasets](https://github.com/uzh-rpg/event-based_vision_resources/blob/master/README.md#datasets-sorted-by-topic).\n", 22 | "\n", 23 | "This exercise is not as guided as the first one. Instead of filling in the blanks, here we show the plots and you are asked to write code to produce such plots (approximately). You may use tools such as numpy and matplotlib. If possible, try also to write it nicely, using resuable functions." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "# Data format\n", 31 | "\n", 32 | "Events in the txt files of the IJRR'17 dataset are given one per line. For example, the first ten lines of the slider_depth txt file are:\n", 33 | "\n", 34 | " 0.003811000 96 133 0\n", 35 | " 0.003820001 127 171 0\n", 36 | " 0.003836000 4 160 0\n", 37 | " 0.003837000 149 122 0\n", 38 | " 0.003848001 63 121 1\n", 39 | " 0.003849001 17 144 1\n", 40 | " 0.003852000 92 119 0\n", 41 | " 0.003866001 16 137 1\n", 42 | " 0.003875000 156 71 0\n", 43 | " 0.003879000 26 149 0\n", 44 | "\n", 45 | "That is, data is given in the form:\n", 46 | "\n", 47 | " t, x, y, p\n", 48 | "\n", 49 | "timestamp $t$ (in seconds), $x$ pixel coordinate (horizontal or column index), $y$ pixel coordinate (vertical coordinate or row index) and polarity $p$ (1 bit of information with the sign of the brightness change: 1 if positive, 0 if negative). Since the DAVIS240C has a spatial resolution of 240 x 180 pixels, $x$ and $y$ adopt values in $\\{0,\\ldots,239\\}$ and $\\{0,\\ldots,179\\}$, respectively.\n", 50 | "\n", 51 | "You first task is to read the data from file (loading it into temporal variables)." 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 1, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "import os\n", 61 | "import numpy as np\n", 62 | "from matplotlib import pyplot as plt" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Here is some code to read the data in a txt file with the format shown above" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 2, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "# Simple codre. There may be more efficient ways.\n", 79 | "def extract_data(filename):\n", 80 | " infile = open(filename, 'r')\n", 81 | " timestamp = []\n", 82 | " x = []\n", 83 | " y = []\n", 84 | " pol = []\n", 85 | " for line in infile:\n", 86 | " words = line.split()\n", 87 | " timestamp.append(float(words[0]))\n", 88 | " x.append(int(words[1]))\n", 89 | " y.append(int(words[2]))\n", 90 | " pol.append(int(words[3]))\n", 91 | " infile.close()\n", 92 | " return timestamp,x,y,pol" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 3, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "filename_sub = 'slider_depth/events_chunk.txt'\n", 102 | "# Call the function to read data \n", 103 | "timestamp, x, y, pol = extract_data(filename_sub)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "For this exercise, let us provide the sensor size (height, width)" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "img_size = (180,240)" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "# Space-time plot\n", 127 | "\n", 128 | "The following figure shows a visualization of (the first 2000) events in space-time (image plane of the camera amd time).\n", 129 | "\n", 130 | "![Events, space-time and polarity](images/space_time_pol.png)\n", 131 | "\n", 132 | "Let us first try to plot something simpler (with fewer dimensions).\n", 133 | "The following plots were generated with events between $N_e=5000$ and $N_e = 50000$.\n", 134 | "\n", 135 | "\n", 136 | "# Image-like (2D grid) representation\n", 137 | "\n", 138 | "## Histograms of events (Event count)\n", 139 | "You are asked to write Python code to create the following image. It has been generated by accumulating the event polarities pixel-wise from the first $N_e=5000$ in the file.\n", 140 | "\n", 141 | "![balance_polarities_gray](images/balance_polarities_gray.png)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 5, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "# Write code..." 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "- What do the \"three colors\" in the image represent?\n", 158 | "- What is the maximum number of positive events at any pixel?\n", 159 | "- and the maximum number of negative events at any pixel?\n", 160 | "- What could such an image be used for?" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 6, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "# Write your answers..." 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "Next, consider using [pseudocolor](https://en.wikipedia.org/wiki/False_color) to display the events. Write code to generate the following image.\n", 177 | "\n", 178 | "![balance_polarities_red_blue](images/balance_polarities_red_blue.png)\n", 179 | "\n", 180 | "- What do the white, red and blue colored pixels represent?\n", 181 | "- Why is blue color used instead of green color?" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "Please write code to plot an image where every pixel may have only three possible values (ternary image): -1, 0, 1. You may set every pixel using, for example, the last event polarity at that pixel.\n", 189 | "\n", 190 | "![ternary_gray](images/ternary_gray.png)\n", 191 | "\n", 192 | "- What could this type of representation be good for compared to the histogram one (above)?\n", 193 | "\n", 194 | "Next, you are asked to split by polarity, that is, to compute and plot one histogram of events for each event polarity. One for positive events and one for negative events, as shown next.\n", 195 | "\n", 196 | "![](images/hist2d_pos_veridi.png)\n", 197 | "![](images/hist2d_neg_veridi.png)\n", 198 | "\n", 199 | "- Can you clearly identify both polarities in the moving edges?" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "## Images of timestamps\n", 207 | "\n", 208 | "Other useful representations are those that, instead of counting events at every pixel, just show a timestamp per pixel. It could be the last timestamp, an average timestamp or a timestamp composed with an exponential decay.\n", 209 | "They are also called \"time surfaces\", \"time maps\" or \"surface of active events\".\n", 210 | "\n", 211 | "### Time maps with exponential decay\n", 212 | "\n", 213 | "Next, write code to replicate the type of time-images of the [2015 PAMI paper HOTS, Fig. 2](https://www.neuromorphic-vision.com/public/publications/1/publication.pdf).\n", 214 | "Use 50000 events to better see the traces of the edges as they trigger events when they move through the image plane.\n", 215 | "\n", 216 | "$ image(x,y; t) = exp(-|t-T(x,y)| / \\tau) $\n", 217 | "\n", 218 | "This is equation (3) in the above paper. The paper uses $\\tau = 50$ ms (see the bottom of page 8). For the plots below, a value $\\tau = 30$ ms is used. $\\tau$ is a tunable parameter that depends on the motion in the scene.\n", 219 | "Note that the paper uses a neighborhood (\"context\") of size $(2R+1)\\times (2R+1)$ centered at the last event. Instead, you are aked to visualize the time surface using all image pixels (not a subset of them).\n", 220 | "\n", 221 | "Also note that the paper uses the polarity $p$ as an argument of the time map $T$, while here it is not explicitly written. The following figure shows both polarities on the same time map, which is not easy to write with such a notation.\n", 222 | "\n", 223 | "![](images/ts_exp_pol.png)\n", 224 | "\n", 225 | "Next, you are asked to split by polarity, creating one plot for each event polarity:\n", 226 | "\n", 227 | "![](images/ts_exp_pos.png)\n", 228 | "![](images/ts_exp_neg.png)\n", 229 | "\n", 230 | "- Describe what you see in this representation and whether it is better or not to split by polarity. In what situations?\n", 231 | "- Is there the same amount of noise on both type of events (positive, negative)?\n", 232 | "\n", 233 | "" 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "metadata": {}, 240 | "source": [ 241 | "### Average timestamp images.\n", 242 | "In this type of representation, each pixel contains the average timestamp of the events that happened in it in the last few milliseconds. (There is no exponential decay, just an average).\n", 243 | "\n", 244 | "![](images/t_ave_both_pols.png)\n", 245 | "\n", 246 | "Next, split by polarity:\n", 247 | "\n", 248 | "![](images/t_ave_pos.png)\n", 249 | "![](images/t_ave_neg.png)\n", 250 | "\n", 251 | "- Describe what you oberve compared to previous representations." 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": {}, 257 | "source": [ 258 | "# Space-time plots\n", 259 | "\n", 260 | "Next, we take a look at different ways to visualize events in space-time. Such a space-time is obtained by considering a temporal window of the signal that is output by the camera as a response of the light arriving at the image plane (i.e., the \"retina\").\n", 261 | "\n", 262 | "## Point set\n", 263 | "\n", 264 | "Write Python code to plot the first $N_e = 2000$ events in space-time, like a point set or point \"cloud\":\n", 265 | "\n", 266 | "![Events, space-time and polarity](images/space_time_pol.png)\n", 267 | "\n", 268 | "You may find the [3D scatterplot example](https://matplotlib.org/3.1.1/gallery/mplot3d/scatter3d.html) useful.\n", 269 | "\n", 270 | "Try experimenting by moving around the viewpoint (clicking on the figure generated by Python's matplotlib).\n", 271 | "\n", 272 | "- For us, humans, is the information more intelligible from any specific viewpoint?\n", 273 | "- What information do you gain by looking at this point cloud from directions parallel to each of the coordinate axes?\n", 274 | "\n", 275 | "Then, write a function to generate a short **[movie](movie.mp4)** that smoothly shows the intermediate viewpoints between two specified ones (the start and end viewpoints). See the following VLC player screenshot.\n", 276 | "Suggestion: use the matplotlib command `ax.view_init(azim=XXX,elev=ZZZ)`. Write images to disk and create a movie from them using ffmpeg. There is no need to install ffmpeg; you may use the [static build](https://johnvansickle.com/ffmpeg/). Video [coding options](https://trac.ffmpeg.org/wiki/Encode/H.264), such as lossless.\n", 277 | "\n", 278 | "![movie snapthot on VLC](images/movie_vlc_pointset.png)" 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": {}, 284 | "source": [ 285 | "## Voxel grid representation\n", 286 | "\n", 287 | "Next, you are asked to write code to convert the event data into a 3D histogram. To this end, events shall be collected into bins: 1 bin per pixel in space and say 5 bins in time. Feel free to write your own function to compute the 3D histogram or to use numpy's [histogramdd](https://numpy.org/doc/stable/reference/generated/numpy.histogramdd.html?highlight=histogramdd#numpy.histogramdd) function. Actually it is good to compute the histogram using both methods and making sure that both agree.\n", 288 | "\n", 289 | "### Regular histogram\n", 290 | "Write python code to compute the 3D histogram and display it as a voxel grid. \n", 291 | "Every voxel counts the number of events falling (i.e., binned) within it, regardless of polarity.\n", 292 | "Suggestion: [this sample code](https://matplotlib.org/3.1.1/gallery/mplot3d/voxels_rgb.html#sphx-glr-gallery-mplot3d-voxels-rgb-py)\n", 293 | "\n", 294 | "![voxel histogram](images/voxel_nn.png)\n", 295 | "\n", 296 | "- What are the minimum and maximum number of events at any voxel?\n", 297 | "- How many voxels are there? How many are \"occupied\" (i.e., have a non-zero value)? (voxel grids are also known as \"occupancy maps\" in Robotics). What is the ratio between these numbers? (How sparse is the data?)\n", 298 | "\n", 299 | "### Interpolated histogram\n", 300 | "(Smooth histogram that also includes polarity information).\n", 301 | "Next, modify the code you have written to include polarity. That is, instead of counting events on every bin, \"count\" the polarity. Moreover, to make the histogram smoother, use a linear voting scheme by which an event splits its polarity in its two closest temporal bins. The split takes into account the distance from the event to both bins. (This idea of smoothing a histogram is similar to the idea of kernel density estimation). The following plot illustrates this idea of \"smoothing\" the histogram.\n", 302 | "\n", 303 | "![histogram smoothing](images/histogram_smoothing.png)\n", 304 | "\n", 305 | "The next figure shows the voxel grid obtained by using linear voting of polarity (per-voxel balance of linearly-interpolated polarity). Grayscale values are used: dark values represent negative events (voxels with negative balance of polarities) and bright values represent positive events (voxels with positive balance of polarities).\n", 306 | "\n", 307 | "![voxel linear voting with polarity](images/voxel_linvote_pol.png)\n", 308 | "\n", 309 | "- What are the minimum and maximum value of the balance of event polarities at the voxels?\n", 310 | "- Does this way of computing the histogram, including polarity, affect the above \"sparsity ratio\"?\n", 311 | "- How does this (3D) voxel grid representation compare to 2D representations? For example in terms of memory, in terms of preservation of the information contained in the event data?\n", 312 | "- What could the voxel grid representation be good for?\n", 313 | "- How would you interpret the above voxel grid in terms of a continuous \"polarity field\" $p(x,y,t)$ or the temporal derivative of the brightness signal arriving at the sensor $\\frac{\\partial L}{\\partial t}(x,y,t)$?" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "metadata": {}, 319 | "source": [ 320 | "## Try experimenting\n", 321 | "Try to come up with new plots (alternative data representations) that reveal some aspect of the information contained in the event data." 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": null, 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [] 330 | } 331 | ], 332 | "metadata": { 333 | "kernelspec": { 334 | "display_name": "Python 2", 335 | "language": "python", 336 | "name": "python2" 337 | }, 338 | "language_info": { 339 | "codemirror_mode": { 340 | "name": "ipython", 341 | "version": 2 342 | }, 343 | "file_extension": ".py", 344 | "mimetype": "text/x-python", 345 | "name": "python", 346 | "nbconvert_exporter": "python", 347 | "pygments_lexer": "ipython2", 348 | "version": "2.7.12" 349 | } 350 | }, 351 | "nbformat": 4, 352 | "nbformat_minor": 2 353 | } 354 | -------------------------------------------------------------------------------- /images/balance_polarities_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/balance_polarities_gray.png -------------------------------------------------------------------------------- /images/balance_polarities_red_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/balance_polarities_red_blue.png -------------------------------------------------------------------------------- /images/hist2d_neg_veridi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/hist2d_neg_veridi.png -------------------------------------------------------------------------------- /images/hist2d_pos_veridi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/hist2d_pos_veridi.png -------------------------------------------------------------------------------- /images/histogram_smoothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/histogram_smoothing.png -------------------------------------------------------------------------------- /images/movie_vlc_pointset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/movie_vlc_pointset.png -------------------------------------------------------------------------------- /images/space_time_pol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/space_time_pol.png -------------------------------------------------------------------------------- /images/space_time_pol_edge_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/space_time_pol_edge_on.png -------------------------------------------------------------------------------- /images/t_ave_both_pols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/t_ave_both_pols.png -------------------------------------------------------------------------------- /images/t_ave_neg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/t_ave_neg.png -------------------------------------------------------------------------------- /images/t_ave_pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/t_ave_pos.png -------------------------------------------------------------------------------- /images/ternary_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ternary_gray.png -------------------------------------------------------------------------------- /images/ternary_red_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ternary_red_blue.png -------------------------------------------------------------------------------- /images/ts_exp_balance_pol_red_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ts_exp_balance_pol_red_blue.png -------------------------------------------------------------------------------- /images/ts_exp_neg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ts_exp_neg.png -------------------------------------------------------------------------------- /images/ts_exp_pol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ts_exp_pol.png -------------------------------------------------------------------------------- /images/ts_exp_pol_red_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ts_exp_pol_red_blue.png -------------------------------------------------------------------------------- /images/ts_exp_pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/ts_exp_pos.png -------------------------------------------------------------------------------- /images/voxel_linvote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/voxel_linvote.png -------------------------------------------------------------------------------- /images/voxel_linvote_pol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/voxel_linvote_pol.png -------------------------------------------------------------------------------- /images/voxel_nn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/images/voxel_nn.png -------------------------------------------------------------------------------- /movie.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tub-rip/events_viz/dfc6fd27688c70f11e98b349111e35a5aad9a718/movie.mp4 -------------------------------------------------------------------------------- /print_seismic_cmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 7 19:53:18 2020 4 | 5 | @author: ggb 6 | """ 7 | 8 | import matplotlib 9 | import numpy as np 10 | 11 | cmap = matplotlib.cm.get_cmap('seismic') 12 | #rgba = cmap(0.5) 13 | idx = np.linspace(0,1,256) 14 | 15 | print str(cmap(0)[3]) 16 | 17 | iCount = 0 18 | for ii in idx: 19 | #print( np.array(cmap(ii+1)) - np.array(cmap(ii)) ) 20 | print( "lut.at(0," + str(iCount) + ") = cv::Vec3b(" 21 | + str(int(cmap(ii)[0]*255)) + ", " 22 | + str(int(cmap(ii)[1]*255)) + ", " 23 | + str(int(cmap(ii)[2]*255)) + ");") 24 | iCount += 1 25 | --------------------------------------------------------------------------------