wangrong
2025-03-19 291afb9c81aae1d15d9e3edcea618d0a2e1fc5a6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#include "vp_cluster_node.h"
#include "../../third_party/bhtsne/tsne.h"
 
namespace vp_nodes {
    vp_cluster_node::vp_cluster_node(std::string node_name, 
                        bool use_tSNE, 
                        std::vector<std::string> s_labels_to_display,
                        int sampling_frequency,
                        int min_sampling_width,
                        int min_sampling_height,
                        int max_sample_num_for_tsne,
                        int max_sample_num_per_category):
                        vp_node(node_name),
                        use_tSNE(use_tSNE),
                        s_labels_to_display(s_labels_to_display),
                        sampling_frequency(sampling_frequency),
                        min_sampling_width(min_sampling_width),
                        min_sampling_height(min_sampling_height),
                        max_sample_num_for_tsne(max_sample_num_for_tsne),
                        max_sample_num_per_category(max_sample_num_per_category) {
        this->initialized();
    }
    
    vp_cluster_node::~vp_cluster_node() {
        deinitialized();        
    }
 
    // please refer to ../../third_party/trt_vehicle/main/vehicle_cluster.cpp
    void vp_cluster_node::reduce_dims_using_tsne(std::vector<std::vector<float>>& low_features,
                                int no_dims, int max_iter, double perplexity, 
                                double theta, int rand_seed, bool skip_random_init, 
                                int stop_lying_iter, int mom_switch_iter) {
        assert(cache_high_features.size() != 0);
        auto N = cache_high_features.size();
        auto D = cache_high_features[0].second.size();  // all the same as the first feature's dims
 
        // prepare input
        double data[N * D];
        double Y[N * no_dims];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < D; j++) {
                data[i * D + j] = cache_high_features[i].second[j];
            }
        }
        
        // call t-SNE
        TSNE::run(data, N, D, Y, no_dims, perplexity, theta, rand_seed, skip_random_init, max_iter, stop_lying_iter, mom_switch_iter);
 
        // prepare output
        for (int i = 0; i < N; i++) {
            std::vector<float> low_dims_feature;
            for (int j = 0; j < no_dims; j++) {
                low_dims_feature.push_back(float(Y[i * no_dims + j]));
            }
            low_features.push_back(low_dims_feature);
        }
    }
 
 
    std::shared_ptr<vp_objects::vp_meta> vp_cluster_node::handle_frame_meta(std::shared_ptr<vp_objects::vp_frame_meta> meta) {
        // sampling frequency
        if (std::chrono::duration_cast<std::chrono::milliseconds>(NOW - last_sampling_time) < std::chrono::milliseconds(sampling_frequency)) {
            return meta;
        }
        last_sampling_time = NOW;
        
        /* t-SNE */
        if (use_tSNE) {
            for (int i = 0; i < meta->targets.size(); i++) {
                if (meta->targets[i]->width < min_sampling_width || meta->targets[i]->height < min_sampling_height) {
                    continue;
                }
                
                if (cache_high_features.size() >= max_sample_num_for_tsne) {
                    // too many cache, we need control number of samples
                    cache_high_features.erase(cache_high_features.begin());
                }
                // save mat->feature pair to cache
                cv::Mat m;
                cv::Mat roi(meta->frame, cv::Rect(meta->targets[i]->x, meta->targets[i]->y, meta->targets[i]->width, meta->targets[i]->height));
                roi.copyTo(m);
                std::pair<cv::Mat, std::vector<float>> p {m, meta->targets[i]->embeddings};
                cache_high_features.push_back(p);
            }
 
            // at least 10 samples to t-SNE
            if (cache_high_features.size() >= 10) {
                // reduce dims first
                std::vector<std::vector<float>> low_features;
                reduce_dims_using_tsne(low_features);
 
                // now display on screen
                // normalize low dims feature to coordinate of [0:1] and display them on 2D screen
                auto max_x = 0.0f, max_y = 0.0f, min_x = 0.0f, min_y = 0.0f;
                for(int i = 0; i < low_features.size(); i++) {
                    auto& f = low_features[i];
                    // 2 values in f
                    max_x = std::max(max_x, f[0]);
                    max_y = std::max(max_y, f[1]);
                    min_x = std::min(min_x, f[0]);
                    min_y = std::min(min_y, f[1]);
                }
                auto x_range = max_x - min_x;
                auto y_range = max_y - min_y;
 
                // draw on (tsne_canvas_w_h + tsne_thumbnail_w_h) * (tsne_canvas_w_h + tsne_thumbnail_w_h)
                cv::Mat canvas(tsne_canvas_w_h + tsne_thumbnail_w_h, tsne_canvas_w_h + tsne_thumbnail_w_h, CV_8UC3, cv::Scalar(127, 127, 127));
                for(int i = 0; i < low_features.size(); i++) {
                    auto& f = low_features[i];
                    // convert to [0:1]
                    f[0] = (f[0] - min_x) / x_range;
                    f[1] = (f[1] - min_y) / y_range;
 
                    auto& img = cache_high_features[i].first;
                    cv::Mat img_tmp;
                    cv::resize(img, img_tmp, cv::Size(tsne_thumbnail_w_h, tsne_thumbnail_w_h));
                    cv::rectangle(img_tmp, cv::Rect(0, 0, img_tmp.cols, img_tmp.rows), cv::Scalar(255, 0, 0));
                    cv::Mat roi(canvas, cv::Rect(int(f[0] * tsne_canvas_w_h), int(f[1] * tsne_canvas_w_h), tsne_thumbnail_w_h, tsne_thumbnail_w_h));
                    img_tmp.copyTo(roi);
                }
 
                cv::imshow("cluster using features powered by t-SNE", canvas);
            }
        }
 
        /* categories */
        for (int i = 0; i < meta->targets.size(); i++) {
            auto& t = meta->targets[i];
            if (t->width < min_sampling_width || t->height < min_sampling_height) {
                continue;
            }
 
            cv::Mat m;
            cv::Mat roi(meta->frame, cv::Rect(t->x, t->y, t->width, t->height));
            roi.copyTo(m);
 
            for (int j = 0; j < t->secondary_labels.size(); j++) {
                auto& category = t->secondary_labels[j];
                bool filter_pass = false;
                // all
                if (s_labels_to_display.size() == 0) {
                    cache_categories[category].push_back(m);
                    filter_pass = true;
                }
                else {
                    // has a filter
                    if (std::find(s_labels_to_display.begin(), s_labels_to_display.end(), category) != s_labels_to_display.end()) {
                        cache_categories[category].push_back(m);
                        filter_pass = true;
                    }
                }
 
                if (filter_pass) {                
                    // too many cache, we need control number of samples
                    if (cache_categories[category].size() >= max_sample_num_per_category) {
                        cache_categories[category].erase(cache_categories[category].begin());
                    }
                }
            }
        }
        // display on screen
        // calculate total number of rows
        auto rows_num = 0; 
        for (auto& p: cache_categories) {
            auto num = p.second.size() / category_num_per_row;
            if (p.second.size() % category_num_per_row != 0 || num == 0) {
                num++;
            }
            rows_num += num;
        }
        
        // draw on (category_canvas_w) * (category_canvas_h)
        auto category_canvas_w = (category_thumbnail_w_h + category_gap) * (category_num_per_row + 1);
        auto category_canvas_h = (category_thumbnail_w_h + category_gap) * (rows_num + 1);
        cv::Mat canvas(category_canvas_h, category_canvas_w, CV_8UC3, cv::Scalar(127, 127, 127));
 
        auto row_index = 0;
        for (auto& p: cache_categories) {
            auto col_index = 0;
            auto& category_items = p.second;
            auto& category_name = p.first;
            cv::putText(canvas, category_name, cv::Point(10, (category_gap + category_thumbnail_w_h) * row_index + category_gap), 1, 1, cv::Scalar(0, 0, 255));
 
            bool new_row = false;
            for (int i = 0; i < category_items.size(); i++) {
                cv::Mat img_tmp;
                cv::resize(category_items[i], img_tmp, cv::Size(category_thumbnail_w_h, category_thumbnail_w_h));
                cv::Mat roi(canvas, cv::Rect((category_gap + category_thumbnail_w_h) * col_index + category_gap, (category_gap + category_thumbnail_w_h) * row_index + category_gap, category_thumbnail_w_h, category_thumbnail_w_h));
                img_tmp.copyTo(roi);
 
                col_index++;
                if ((col_index + 1) % category_num_per_row == 0) {
                    col_index = 0;
                    row_index ++;
                    new_row = true;
                } 
                else {
                    new_row = false;
                }
            }
            if (!new_row) {
                row_index++;
            }
        }
 
        cv::imshow("cluster using labels", canvas);
 
        return meta;
    }
 
    std::shared_ptr<vp_objects::vp_meta> vp_cluster_node::handle_control_meta(std::shared_ptr<vp_objects::vp_control_meta> meta) {
        return meta;
    }
}