0

I am working with tracking elements video using opencv (basically counting number of elements after hsv thresholding). I have a deque buffer to store centroid positions. I chose a limited buffer of 64 (~2 seconds on 30 fps, could be longer). My goal is to save the data into .csv file in such a format that I can readily use later (see below). Additionally, I am counting the number of detected regions. The format would be like

cX  cY  number
444 265   19
444 265   19
444 264   19
444 264   19
...

With cX being the centroid in X and cY the centroid in Y of the largest element, and the number of detected regions. Column naming is not the main goal although it would be nice.

For display purposes, I need to have the centroid as tuple. I make them grow frame by frame using appendleft:

center_points = deque(maxlen=64)
object_number = deque(maxlen=64)
iteration_counter = 1

    while True


        # read video frames..
        # do stuff...
        # get contours
            my_cnts = cv2.findContours(...)
        # get largest object
            c = max(my_cnts, key=cv2.contourArea)
            ((x, y), radius) = cv2.minEnclosingCircle(c)
            M = cv2.moments(c)
            big_center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
# count object number as int name it 'num'

center_points.appendleft(big_center)
object_number.appendleft(num)

Now, when the buffer is full, I want to save the data into file):

# Convert to array to save
    # Wait until the iteration number is divisible by the buffer length
    if(iteration_number % 64 == 0):
        print("Saving on iteration..." + str(iteration_number))
        array_to_save = np.array([center_points, object_number]).T


        with open(filename,'a') as outfile:
            np.savetxt(outfile, array_to_save,
                       delimiter=',', fmt='%s')
# Add 1 to the counter
    iteration_number = iteration_number + 1

Problem

The code above works and writes something that looks like this:

(444 265) 19
(444 265) 19
(444 264) 19
(444 263) 19

I would like to do something like np.array(center_points) and bind that to object_number. I have had trouble with dimensions (e.g, (64,2) and (64) not being compatible). I have tried np.append and np.stack but can't find the correct way of formatting the data.

Else, I could keep the code as is but I would like to somehow get rid of the parenthesis on columns 1 and 2 and save that object instead (have tried regular expressions on array_to_save without success). All three columns should be numeric or saved as string but easily retrieved as numeric later in reading.

Update

Based on comments I tried

array_to_save = np.concatenate([np.array(center_points), object_number[:, None]])
    TypeError: sequence index must be integer, not 'tuple'

I also tried

array_to_save = np.concatenate([np.array(center_points), np.array(object_number)[:, None]])
    ValueError: all the input array dimensions except for the concatenation axis must match exactly
Matias Andina
  • 4,029
  • 4
  • 26
  • 58
  • You can use `np.concatenate(x, y[:, None])`, i.e. add extra column dimension to have `(64, 2)` and `(64, 1)`, then you can concatenate. – a_guest Jan 14 '19 at 00:22
  • @a_guest This does not work am I running correctly? `array_to_save = np.concatenate(np.array(center_points), object_number[:, None]) TypeError: sequence index must be integer, not 'tuple'` – Matias Andina Jan 14 '19 at 01:17
  • You need to enclose the arguments in an additional sequence, i.e. `np.concatenate([np.array(center_points), object_number[:, None]])`. – a_guest Jan 14 '19 at 14:42
  • @a_guest Please see update. – Matias Andina Jan 14 '19 at 19:23
  • Well it looks like `object_number` does not have as many items as `center_points`. Anyway the general rule is that along the common axis, in our case this is the `0-th` axis, the two arrays to be concatenated need to have the same length. You can check the length in each dimension by `.shape`. I.e. do `c = np.array(center_points); o = np.array(object_number)[:, None]` and then check `c.shape` and `o.shape`. Since `shape[0]` is the common axis it needs to be the same number. For example `c.shape == (60, 2); o.shape == (60, 1)` would be fine and `c.shape == (60, 2); o.shape == (58, 1)` is not. – a_guest Jan 15 '19 at 07:19
  • this ended up working, you should post an answer to the question! – Matias Andina Jan 15 '19 at 22:10

2 Answers2

1

You can concatenate the arrays along the column dimension in order to create a (X, 3) array out of the (X, 2) and (X,) array. In order to be ready for concatenation all the arrays need to have the same number of dimensions and hence you need to add an extra dimension to the flat array object_number: (X,) -> (X, 1). This can by done via object_number[:, np.newaxis] or object_number[:, None]. The complete solution then is:

np.concatenate([np.array(center_points),
                np.array(object_number)[:, None]], axis=-1)
a_guest
  • 34,165
  • 12
  • 64
  • 118
0

Part of your difficulty, I think, is that np.savetxt() does not work well with tuples held in numpy arrays. I've developed some test code that I think replicates the key aspects of your problem and provides solutions to them:

import numpy as np
from collections import deque

# Create test data
center_points = deque(maxlen=64)
number = deque(maxlen=64)
for i in range(10):
    big_center = (i*3,i*100)
    center_points.appendleft(big_center)
    number.appendleft(19)

# Write the test data

array_to_save = np.array([center_points,number]).T
print (array_to_save)
with open("test.txt","w") as outfile:
    outfile.write("\n".join([" ".join([str(a[0]),str(a[1]),str(b)]) for a,b in
        array_to_save]))

# Re-read the test data
center_points2 = deque(maxlen=64)
number2 = deque(maxlen=64)

with open("test.txt","r") as infile:
    for line in infile:
        x = [int(xx) for xx in line.split()]
        center_points2.append((x[0],x[1]))
        number2.append(x[2])
    new_array = np.array([center_points2,number2]).T

print (new_array)

When run, this code outputs the following, showing that the original array_to_save is identical to the new_array that has been read back in:

[[(27, 900) 19]
 [(24, 800) 19]
 [(21, 700) 19]
 [(18, 600) 19]
 [(15, 500) 19]
 [(12, 400) 19]
 [(9, 300) 19]
 [(6, 200) 19]
 [(3, 100) 19]
 [(0, 0) 19]]
[[(27, 900) 19]
 [(24, 800) 19]
 [(21, 700) 19]
 [(18, 600) 19]
 [(15, 500) 19]
 [(12, 400) 19]
 [(9, 300) 19]
 [(6, 200) 19]
 [(3, 100) 19]
 [(0, 0) 19]]

The file test.txt is as follows:

27 900 19
24 800 19
21 700 19
18 600 19
15 500 19
12 400 19
9 300 19
6 200 19
3 100 19
0 0 19

The file reading and writing code in this version is a little more complicated than just calling np.savetxt() but it handles the tuples explicitly.

Update

Alternatively, if you preferred to do all the manipulation in the numpy arrays, you could use:

import numpy as np
from collections import deque

# Create test data
center_points = deque(maxlen=64)
number = deque(maxlen=64)
for i in range(10):
    big_center = (i*3,i*100)
    center_points.appendleft(big_center)
    number.appendleft(19)

print (center_points)
print (number)

# Write the test data

x, y = zip(*center_points)
array_to_save = np.array([x,y,number]).T
print (array_to_save)
np.savetxt("test.txt", array_to_save, fmt="%d")

# Re-read the test data
new_array = np.loadtxt("test.txt", dtype=int)
print (new_array)

center_points2 = deque(zip(new_array.T[0],new_array.T[1]),maxlen=64)
number2 = deque(new_array.T[2],maxlen=64)
print (center_points2)
print (number2)

This uses the approach described in Transpose/Unzip Function (inverse of zip)? to separate the two elements of each tuple into two lists that are then included with the number list into a single numpy array that can be saved with savetxt() and re-loaded with loadtxt().

The print() calls are just to illustrate that the data that the program finishes with is exactly the same as the data that it started with. They produce the following output:

deque([(27, 900), (24, 800), (21, 700), (18, 600), (15, 500), (12, 400), (9, 300), (6, 200), (3, 100), (0, 0)], maxlen=64)
deque([19, 19, 19, 19, 19, 19, 19, 19, 19, 19], maxlen=64)
[[ 27 900  19]
 [ 24 800  19]
 [ 21 700  19]
 [ 18 600  19]
 [ 15 500  19]
 [ 12 400  19]
 [  9 300  19]
 [  6 200  19]
 [  3 100  19]
 [  0   0  19]]
[[ 27 900  19]
 [ 24 800  19]
 [ 21 700  19]
 [ 18 600  19]
 [ 15 500  19]
 [ 12 400  19]
 [  9 300  19]
 [  6 200  19]
 [  3 100  19]
 [  0   0  19]]
deque([(27, 900), (24, 800), (21, 700), (18, 600), (15, 500), (12, 400), (9, 300), (6, 200), (3, 100), (0, 0)], maxlen=64)
deque([19, 19, 19, 19, 19, 19, 19, 19, 19, 19], maxlen=64)
Simon
  • 10,679
  • 1
  • 30
  • 44
  • This solution could potentially work, although I would prefer dealing with the data before saving and having a way to deal with tuples going into the np.array, transforming them either before or after they were concatenated. Any ideas on that? – Matias Andina Jan 14 '19 at 19:24