├── .github └── FUNDING.yml ├── main.cpp ├── README.md ├── huffman.hpp └── model_utils.hpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: miaoerduo 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // dlib_utils 4 | // 5 | // Created by zhaoyu on 2018/1/8. 6 | // Copyright © 2018 zhaoyu. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | int main(int argc, const char * argv[]) { 15 | 16 | if (argc != 3) { 17 | std::cout << "Usage: ./main.out src_path dest_path" << std::endl; 18 | return 0; 19 | } 20 | 21 | dlib::shape_predictor sp; 22 | 23 | // load model 24 | dlib::deserialize(argv[1]) >> sp; 25 | 26 | // compress model 27 | med::save_shape_predictor_model(sp, argv[2], 0.0001, 512); 28 | 29 | // load compressed model 30 | dlib::shape_predictor sp2; 31 | med::load_shape_predictor_model(sp2, argv[2]); 32 | 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dlib-face-landmark-compression 2 | 3 | Dlib中的人脸landmark检测的模型压缩 4 | 5 | 具体介绍见博客:[https://www.miaoerduo.com/2018/01/08/dlib-landmark-model-compression/](https://www.miaoerduo.com/2018/01/08/dlib-landmark-model-compression/) 6 | 7 | 使用前,需要修改dlib的源码: 8 | DLIB_PATH/include/dlib/image_processing/image_processing/shape_predictor.h 9 | 10 | 修改 shape_predictor 类的成员变量的属性为public。 11 | 12 | **另外,有同学反映说不能正常加载模型,后来换了dlib 19.8之后,就可以了。这里也请大家出现类似的问题注意一下。** 13 | 14 | **请注意,压缩之后的模型,存储格式发生变化,因此是不能通过dlib原本的方式去加载的,要换成下面的方式:** 15 | 16 | ``` 17 | dlib::shape_predictor sp; 18 | med::load_shape_predictor_model(sp, "/path/to/compressed_model"); 19 | ``` 20 | 21 | 需要使用该项目的同学,只需要加`huffman.hpp`和`model_utils.hpp`加入项目中即可。 22 | 23 | 编译方式如下,建议每个人先运行`main.cpp`的Demo: 24 | 25 | ``` 26 | git clone https://github.com/miaoerduo/dlib-face-landmark-compression.git 27 | cd dlib-face-landmark-compression 28 | g++ main.cpp -o main.bin -O2 -I ./ -I DLIB_PATH/include -L DLIB_PATH/lib -ldlib -std=c++11 29 | ./main.bin src_dlib_shape_predictor_model dest_model 30 | ``` 31 | 32 | 为了方便大家的调试,这里上传一个dlib的68点landmark的原模型和使用main.cpp的代码压缩之后的模型。 33 | 34 | 链接: https://pan.baidu.com/s/1z1Sh-ljCBrV_Rorsn2eOxA 提取码: t5mc 35 | -------------------------------------------------------------------------------- /huffman.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // huffman.hpp 3 | // dlib_utils 4 | // 5 | // Created by zhaoyu on 2018/1/8. 6 | // Copyright © 2018 zhaoyu. All rights reserved. 7 | 8 | #ifndef huffman_h 9 | #define huffman_h 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace med { 16 | 17 | // A Huffman Tree Node 18 | struct HuffmanTree { 19 | int c; // character in an alphabet 20 | unsigned long cfreq; // frequency of c. 21 | struct HuffmanTree *left; 22 | struct HuffmanTree *right; 23 | HuffmanTree(int c, int cfreq, struct HuffmanTree *left=NULL, 24 | struct HuffmanTree *right=NULL) : 25 | c(c), cfreq(cfreq), left(left), right(right) { 26 | } 27 | ~HuffmanTree() { 28 | delete left, delete right; 29 | } 30 | // Compare two tree nodes 31 | class Compare { 32 | public: 33 | bool operator()(HuffmanTree *a, HuffmanTree *b) { 34 | return a->cfreq > b->cfreq; 35 | } 36 | }; 37 | }; 38 | 39 | /** 40 | * Builds a Huffman Tree from an input of alphabet C, where C is a vector 41 | * of (character, frequency) pairs. 42 | */ 43 | HuffmanTree *build_tree(std::vector< std::pair > &alph) { 44 | // First build a min-heap 45 | // Build leaf nodes first 46 | std::priority_queue, HuffmanTree::Compare > alph_heap; 47 | for (auto it = alph.begin(); it != alph.end(); ++ it) { 48 | HuffmanTree *leaf = new HuffmanTree(it->first, it->second); 49 | alph_heap.push(leaf); 50 | } 51 | 52 | // HuffmanTree algorithm: Merge two lowest weight leaf nodes until 53 | // only one node is left (root). 54 | HuffmanTree *root = NULL; 55 | while (alph_heap.size() > 1) { 56 | HuffmanTree *l, *r; 57 | l = alph_heap.top(); 58 | alph_heap.pop(); 59 | r = alph_heap.top(); 60 | alph_heap.pop(); 61 | root = new HuffmanTree(0, l->cfreq + r->cfreq, l, r); 62 | alph_heap.push(root); 63 | } 64 | 65 | return root; 66 | } 67 | 68 | void destroy_tree(HuffmanTree *root) { 69 | delete root; 70 | } 71 | 72 | typedef std::vector code_t; 73 | typedef std::unordered_map codetable; 74 | /** 75 | * Makes a lookup table (std::unordered_map) of (c -> code) from a HuffmanTree, where 76 | * code is an unsigned long representing the binary code. 77 | */ 78 | std::unordered_map build_lookup_table(HuffmanTree *htree) { 79 | codetable lookup; 80 | std::deque< std::pair > q; 81 | 82 | q.push_back(std::make_pair(htree, code_t())); 83 | while (!q.empty()) { 84 | HuffmanTree *node, *lc, *rc; 85 | code_t code; 86 | node = q.front().first; 87 | code = q.front().second; 88 | q.pop_front(); 89 | lc = node->left; 90 | rc = node->right; 91 | if (lc) { 92 | // HuffmanTree is always full (either no children or two children) 93 | // Left child is appended a 0 and right child a 1. 94 | code_t code_cp(code); 95 | q.push_back(std::make_pair(lc, (code.push_back(0), code))); 96 | q.push_back(std::make_pair(rc, (code_cp.push_back(1), code_cp))); 97 | } else { 98 | // Leaf node: contains the character 99 | lookup.insert(std::make_pair(node->c, code)); 100 | } 101 | } 102 | 103 | return lookup; 104 | } 105 | 106 | HuffmanTree * build_tree_from_lookup_table(const codetable &m) { 107 | HuffmanTree *root = new HuffmanTree(0, 0); 108 | for (auto &it: m) { 109 | HuffmanTree * t = root; 110 | for (int idx = 0; idx < it.second.size(); ++ idx) { 111 | if (it.second[idx] == false) { 112 | // left 113 | if (!t->left) t->left = new HuffmanTree(0, 0); 114 | t = t->left; 115 | } else { 116 | // right 117 | if (!t->right) t->right = new HuffmanTree(0, 0); 118 | t = t->right; 119 | } 120 | } 121 | t->c = it.first; 122 | } 123 | return root; 124 | } 125 | } 126 | 127 | #endif /* huffman_h */ 128 | -------------------------------------------------------------------------------- /model_utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // model_utils.hpp 3 | // dlib_utils 4 | // 5 | // Created by zhaoyu on 2018/1/8. 6 | // Copyright © 2018 zhaoyu. All rights reserved. 7 | // 8 | 9 | #ifndef model_utils_h 10 | #define model_utils_h 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace med { 21 | 22 | /** 23 | * data type definition 24 | */ 25 | typedef char int8; 26 | typedef short int16; 27 | typedef int int32; 28 | typedef long long int64; 29 | typedef float float32; 30 | typedef double float64; 31 | typedef unsigned char uint8; 32 | typedef unsigned short uint16; 33 | typedef unsigned int uint32; 34 | typedef unsigned long long uint64; 35 | 36 | /** 37 | * utils for reading and writing 38 | */ 39 | template 40 | void write_single_value(std::ofstream &os, const T &data) { 41 | os.write(reinterpret_cast(&data), sizeof(T)); 42 | } 43 | 44 | template 45 | void read_single_value(std::ifstream &is, T &data) { 46 | is.read(reinterpret_cast(&data), sizeof(T)); 47 | } 48 | 49 | void write_char_vec(std::ofstream &os, const std::vector &data) { 50 | os.write(&data[0], data.size()); 51 | } 52 | 53 | void read_char_vec(std::ifstream &is, std::vector &data, unsigned long size) { 54 | std::vector _data(size, 0); 55 | is.read(reinterpret_cast(&_data[0]), size * sizeof(char)); 56 | data.swap(_data); 57 | } 58 | 59 | void read_float_vec(std::ifstream &is, std::vector &data, unsigned long size) { 60 | std::vector _data(size, 0); 61 | is.read(reinterpret_cast(&_data[0]), size * sizeof(float)); 62 | data.swap(_data); 63 | } 64 | 65 | void bits_to_chars(const std::vector &bits, std::vector &out) { 66 | unsigned long bits_num = bits.size(); 67 | std::vector data((bits_num + 7)/ 8, 0); 68 | 69 | for (int idx = 0; idx < bits_num; ++ idx) { 70 | int c = idx / 8; 71 | int p = idx % 8; 72 | int v = bits[idx]; 73 | data[c] |= (v << (7 - p)); 74 | } 75 | out.swap(data); 76 | } 77 | 78 | void chars_to_bits(const std::vector &chars, std::vector &out, unsigned long bit_num) { 79 | std::vector data(bit_num, false); 80 | for (int idx = 0; idx < bit_num; ++ idx) { 81 | int c = idx / 8; 82 | int p = idx % 8; 83 | data[idx] = chars[c] & (1 << (7 - p)); 84 | } 85 | out.swap(data); 86 | } 87 | 88 | /** 89 | * compress shape predictor model 90 | */ 91 | void save_shape_predictor_model( 92 | dlib::shape_predictor &sp, 93 | const std::string &save_path, 94 | const float _prune_thresh=0.0001, 95 | const unsigned long long _quantization_num=512, 96 | const unsigned long long _version=0) { 97 | 98 | using namespace std; 99 | std::ofstream os(save_path, std::ofstream::binary); 100 | 101 | /** 102 | * const value 103 | */ 104 | const uint64 version = _version; 105 | const uint64 cascade_depth = sp.forests.size(); 106 | const uint64 num_trees_per_cascade_level = sp.forests[0].size(); 107 | const uint64 num_leaves = sp.forests[0][0].num_leaves(); 108 | const uint64 tree_depth = static_cast(std::log(num_leaves) / std::log(2)); 109 | const uint64 feature_pool_size = sp.anchor_idx[0].size(); 110 | const uint64 landmark_num = sp.initial_shape.size() / 2; 111 | const uint64 quantization_num = _quantization_num; 112 | const float32 prune_thresh = _prune_thresh; 113 | 114 | 115 | /** 116 | * save const value 117 | */ 118 | unsigned long long data_length = 7 * sizeof(uint64) + sizeof(float32); 119 | write_single_value(os, data_length); 120 | write_single_value(os, version); 121 | write_single_value(os, cascade_depth); 122 | write_single_value(os, num_trees_per_cascade_level); 123 | write_single_value(os, tree_depth); 124 | write_single_value(os, feature_pool_size); 125 | write_single_value(os, landmark_num); 126 | write_single_value(os, quantization_num); 127 | write_single_value(os, prune_thresh); 128 | 129 | /** 130 | * initial_shape 131 | */ 132 | data_length = landmark_num * 2 * sizeof(float32); 133 | write_single_value(os, data_length); 134 | 135 | for (auto it = sp.initial_shape.begin(); it != sp.initial_shape.end(); ++ it) { 136 | auto data = static_cast(*it); 137 | write_single_value(os, data); 138 | } 139 | 140 | /** 141 | * anchor_idx 142 | */ 143 | data_length = cascade_depth * feature_pool_size * sizeof(uint8); 144 | write_single_value(os, data_length); 145 | 146 | for (int r = 0; r < cascade_depth; ++ r) { 147 | for (int c = 0; c < feature_pool_size; ++ c) { 148 | uint8 idx = static_cast(sp.anchor_idx[r][c]); 149 | write_single_value(os, idx); 150 | } 151 | } 152 | 153 | /** 154 | * deltas 155 | */ 156 | data_length = cascade_depth * feature_pool_size * 2 * sizeof(float32); 157 | write_single_value(os, data_length); 158 | for (int r = 0; r < cascade_depth; ++ r) { 159 | for (int c = 0; c < feature_pool_size; ++ c) { 160 | write_single_value(os, static_cast(sp.deltas[r][c](0))); 161 | write_single_value(os, static_cast(sp.deltas[r][c](1))); 162 | } 163 | } 164 | 165 | /** 166 | * forests 167 | */ 168 | 169 | /** 170 | * splits 171 | */ 172 | data_length = cascade_depth * num_trees_per_cascade_level * (num_leaves - 1) * (sizeof(uint16) + sizeof(uint16) + sizeof(float32)); 173 | write_single_value(os, data_length); 174 | for (int r = 0; r < cascade_depth; ++ r) { 175 | for (int c = 0; c < num_trees_per_cascade_level; ++ c) { 176 | auto &tree = sp.forests[r][c]; 177 | auto &splits = tree.splits; 178 | 179 | for (auto &split: splits) { 180 | uint16 idx1 = static_cast(split.idx1); 181 | uint16 idx2 = static_cast(split.idx2); 182 | float32 thresh = static_cast(split.thresh); 183 | write_single_value(os, idx1); 184 | write_single_value(os, idx2); 185 | write_single_value(os, thresh); 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * leaf values 192 | */ 193 | 194 | /*** step 1 prune ***/ 195 | float leaf_min_value = 100000., leaf_max_value = -100000.; 196 | const unsigned long leaf_value_num = landmark_num * 2; 197 | for (int r = 0; r < cascade_depth; ++ r) { 198 | for (int c = 0; c < num_trees_per_cascade_level; ++ c) { 199 | auto &tree = sp.forests[r][c]; 200 | auto &leaf_values = tree.leaf_values; 201 | for (auto &leaf_value: leaf_values) { 202 | for (int idx = 0; idx < leaf_value_num; ++ idx) { 203 | float v = leaf_value(idx); 204 | if (v > leaf_max_value) leaf_max_value = v; 205 | if (v < leaf_min_value) leaf_min_value = v; 206 | if (std::fabs(v) < prune_thresh) leaf_value(idx) = 0.; 207 | } 208 | } 209 | } 210 | } 211 | 212 | /*** step2 quantization ***/ 213 | float32 quantization_precision = (leaf_max_value - leaf_min_value) / quantization_num; 214 | 215 | unordered_map quantization_frequency; // count frequency 216 | 217 | for (int r = 0; r < cascade_depth; ++ r) { 218 | for (int c = 0; c < num_trees_per_cascade_level; ++ c) { 219 | auto &tree = sp.forests[r][c]; 220 | auto &leaf_values = tree.leaf_values; 221 | for (auto &leaf_value: leaf_values) { 222 | for (int idx = 0; idx < leaf_value_num; ++ idx) { 223 | int quantization_value = static_cast(std::round(leaf_value(idx) / quantization_precision)); 224 | ++ quantization_frequency[quantization_value]; 225 | } 226 | } 227 | } 228 | } 229 | 230 | /*** step3 huffman ***/ 231 | 232 | vector >cfvec(quantization_frequency.begin(), quantization_frequency.end()); 233 | 234 | HuffmanTree *htree = build_tree(cfvec); 235 | codetable ctbl = build_lookup_table(htree); 236 | 237 | destroy_tree(htree); 238 | htree = NULL; 239 | 240 | /*** step4 save the code table ***/ 241 | 242 | data_length = sizeof(float32) + sizeof(uint64); //quantization_precision + ctbl size 243 | for (auto it: ctbl) { 244 | // value + code_size + code_t 245 | data_length += sizeof(int) + sizeof(uint8) + (it.second.size() + 7) / 8; 246 | } 247 | write_single_value(os, data_length); 248 | write_single_value(os, quantization_precision); 249 | uint64 ctbl_size = ctbl.size(); 250 | write_single_value(os, ctbl_size); 251 | 252 | // code table content 253 | for (auto it: ctbl) { 254 | int k = it.first; 255 | vector bits = it.second; 256 | uint8 bits_num = static_cast(bits.size()); 257 | std::vector data; 258 | bits_to_chars(bits, data); 259 | write_single_value(os, k); 260 | write_single_value(os, bits_num); 261 | write_char_vec(os, data); 262 | } 263 | 264 | /*** step5 encode ***/ 265 | vector encode_data_bin; 266 | for (int r = 0; r < cascade_depth; ++ r) { 267 | for (int c = 0; c < num_trees_per_cascade_level; ++ c) { 268 | auto &tree = sp.forests[r][c]; 269 | auto &leaf_values = tree.leaf_values; 270 | for (auto &leaf_value: leaf_values) { 271 | for (int idx = 0; idx < leaf_value_num; ++ idx) { 272 | int quantization_value = static_cast(std::round(leaf_value(idx) / quantization_precision)); 273 | auto code = ctbl[quantization_value]; 274 | encode_data_bin.insert(encode_data_bin.end(), code.begin(), code.end()); 275 | } 276 | } 277 | } 278 | } 279 | 280 | std::vector encode_data; 281 | bits_to_chars(encode_data_bin, encode_data); 282 | 283 | data_length = encode_data.size(); 284 | write_single_value(os, data_length); 285 | write_char_vec(os, encode_data); 286 | } 287 | 288 | /** 289 | * load a compressed shape predictor model 290 | */ 291 | void load_shape_predictor_model(dlib::shape_predictor &sp, const std::string&filename) { 292 | 293 | using namespace std; 294 | std::ifstream is(filename, std::ofstream::binary); 295 | 296 | /** 297 | * constant values 298 | */ 299 | uint64 data_length; 300 | uint64 version; 301 | uint64 cascade_depth; 302 | uint64 num_trees_per_cascade_level; 303 | uint64 tree_depth; 304 | uint64 feature_pool_size; 305 | uint64 landmark_num; 306 | uint64 quantization_num; 307 | float32 prune_thresh; 308 | 309 | read_single_value(is, data_length); 310 | read_single_value(is, version); 311 | read_single_value(is, cascade_depth); 312 | read_single_value(is, num_trees_per_cascade_level); 313 | read_single_value(is, tree_depth); 314 | read_single_value(is, feature_pool_size); 315 | read_single_value(is, landmark_num); 316 | read_single_value(is, quantization_num); 317 | read_single_value(is, prune_thresh); 318 | 319 | /** 320 | * initial shape 321 | */ 322 | read_single_value(is, data_length); 323 | 324 | sp.initial_shape.set_size(landmark_num * 2, 1); 325 | 326 | { 327 | std::vector data; 328 | read_float_vec(is, data, landmark_num * 2); 329 | for (int idx = 0; idx < landmark_num * 2; ++ idx) { 330 | sp.initial_shape(idx) = data[idx]; 331 | } 332 | } 333 | 334 | /** 335 | * anchor_idx 336 | */ 337 | read_single_value(is, data_length); 338 | sp.anchor_idx = vector >( 339 | cascade_depth, vector(feature_pool_size, 0)); 340 | 341 | { 342 | vector data; 343 | for (int r = 0; r < cascade_depth; ++ r) { 344 | read_char_vec(is, data, feature_pool_size); 345 | for (int c = 0; c < feature_pool_size; ++ c) { 346 | sp.anchor_idx[r][c] = static_cast(data[c]); 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * deltas 353 | */ 354 | read_single_value(is, data_length); 355 | sp.deltas = std::vector > >( 356 | cascade_depth, std::vector >( 357 | feature_pool_size, dlib::vector())); 358 | 359 | { 360 | std::vector data; 361 | read_float_vec(is, data, cascade_depth * feature_pool_size * 2); 362 | for (int r = 0; r < cascade_depth; ++ r) { 363 | for (int c = 0; c < feature_pool_size; ++ c) { 364 | int idx = r * feature_pool_size + c; 365 | sp.deltas[r][c](0) = data[idx * 2]; 366 | sp.deltas[r][c](1) = data[idx * 2 + 1]; 367 | } 368 | } 369 | } 370 | 371 | /** 372 | * forests 373 | */ 374 | 375 | /** 376 | * splits 377 | */ 378 | 379 | read_single_value(is, data_length); 380 | 381 | sp.forests = std::vector >( 382 | cascade_depth, std::vector( 383 | num_trees_per_cascade_level, dlib::impl::regression_tree())); 384 | 385 | unsigned long split_num = static_cast(std::round(pow(2, tree_depth) - 1)); 386 | uint16 idx1, idx2; 387 | float32 thresh; 388 | for (int r = 0; r < cascade_depth; ++ r) { 389 | for (int c = 0; c < num_trees_per_cascade_level; ++ c) { 390 | auto &tree = sp.forests[r][c]; 391 | auto &splits = tree.splits; 392 | dlib::impl::split_feature fea; 393 | for (int idx = 0; idx < split_num; ++ idx) { 394 | read_single_value(is, idx1); 395 | read_single_value(is, idx2); 396 | read_single_value(is, thresh); 397 | fea.idx1 = static_cast(idx1); 398 | fea.idx2 = static_cast(idx2); 399 | fea.thresh = static_cast(thresh); 400 | splits.push_back(fea); 401 | } 402 | splits.reserve(splits.size()); 403 | } 404 | } 405 | 406 | 407 | /** 408 | * leaf values 409 | */ 410 | 411 | /*** code table ***/ 412 | read_single_value(is, data_length); 413 | float32 quantization_precision = 0.; 414 | read_single_value(is, quantization_precision); 415 | uint64 ctbl_size; 416 | read_single_value(is, ctbl_size); 417 | 418 | unordered_map > ctbl; 419 | 420 | while (ctbl_size > 0) { 421 | int k; 422 | uint8 bit_num; 423 | std::vector data; 424 | read_single_value(is, k); 425 | read_single_value(is, bit_num); 426 | read_char_vec(is, data, (bit_num + 7) / 8); 427 | std::vector bits; 428 | chars_to_bits(data, bits, bit_num); 429 | ctbl[k] = bits; 430 | -- ctbl_size; 431 | } 432 | 433 | auto *huffman_tree = build_tree_from_lookup_table(ctbl); 434 | 435 | /*** read and decode leaf values ***/ 436 | read_single_value(is, data_length); 437 | 438 | { 439 | std::vector leaf_value_chars; 440 | read_char_vec(is, leaf_value_chars, data_length); 441 | 442 | uint64 num_leaves = static_cast(std::round(pow(2, tree_depth))); 443 | uint64 offset = 0; 444 | 445 | for (int c = 0; c < cascade_depth; ++ c) { 446 | for (int r = 0; r < num_trees_per_cascade_level; ++ r) { 447 | auto &tree = sp.forests[c][r]; 448 | auto &leaf_values = tree.leaf_values; 449 | for (int leaf_value_idx = 0; leaf_value_idx < num_leaves; ++ leaf_value_idx) { 450 | dlib::matrix _leaf; 451 | _leaf.set_size(landmark_num * 2, 1); 452 | 453 | for (int _idx = 0; _idx < landmark_num * 2; ++ _idx) { 454 | auto *t = huffman_tree; 455 | while (t->left || t->right) { 456 | int c = offset / 8; 457 | int p = offset % 8; 458 | bool v = leaf_value_chars[c] & (1 << (7 - p)); 459 | if (!v) t = t->left; 460 | else t = t->right; 461 | ++ offset; 462 | } 463 | _leaf(_idx) = t->c * quantization_precision; 464 | } 465 | 466 | leaf_values.push_back(_leaf); 467 | } 468 | leaf_values.reserve(leaf_values.size()); 469 | } 470 | } 471 | } 472 | 473 | destroy_tree(huffman_tree); 474 | } 475 | 476 | } 477 | 478 | #endif /* model_utils_h */ 479 | --------------------------------------------------------------------------------