1. 程式人生 > >darknet原始碼學習 (1) :yolov3 推理過程

darknet原始碼學習 (1) :yolov3 推理過程

最近專案中會頻繁用到yolov3這個目標檢測演算法框架,由於其在速度和精度尤其是小物體檢測的能力上都比較突出所以目前應用面很廣泛,在應用yolov3的過程中經常會遇到一些演算法上的疑點,由於之前沒有好好學習過darknet這個輕量級DL演算法框架所以決定從yolov3入手理清一些darknet以及yolov3的概念,查漏補缺並糾正之前可能錯誤的理解。

在darknet中跑yolov3的準備工作

git clone https://github.com/pjreddie/darknet
cd darknet && make # 編譯darknet,如果需要使用GPU和opencv set GPU=1 CUDNN=1 OPENCV=1
mkdir model && cd model # 建立model資料夾放置darknet模型 wget https://pjreddie.com/media/files/yolov3.weights # 下載yolov3在coco資料上的模型

yolov3檢測

./darknet detector test cfg/coco.data cfg/yolov3.cfg model/yolov3.weights data/dog.jpg # 載入yolov3配置檔案和模型引數進行檢測

# yolov3 log 從36層擷取:0-74層一共53個conv layer其餘都是res layer即shortcut操作,75-105層為yolov3的特徵互動層分為三種尺度
layer filters size input output 36 res 33 52 x 52 x 256 -> 52 x 52 x 256 37 conv 512 3 x 3 / 2 52 x 52 x 256 -> 26 x 26 x 512 1.595 BFLOPs 38 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 39 conv 512 3 x 3 / 1 26 x 26 x 256 ->
26 x 26 x 512 1.595 BFLOPs 40 res 37 26 x 26 x 512 -> 26 x 26 x 512 41 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 42 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 43 res 40 26 x 26 x 512 -> 26 x 26 x 512 44 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 45 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 46 res 43 26 x 26 x 512 -> 26 x 26 x 512 47 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 48 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 49 res 46 26 x 26 x 512 -> 26 x 26 x 512 50 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 51 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 52 res 49 26 x 26 x 512 -> 26 x 26 x 512 53 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 54 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 55 res 52 26 x 26 x 512 -> 26 x 26 x 512 56 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 57 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 58 res 55 26 x 26 x 512 -> 26 x 26 x 512 59 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 60 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 61 res 58 26 x 26 x 512 -> 26 x 26 x 512 62 conv 1024 3 x 3 / 2 26 x 26 x 512 -> 13 x 13 x1024 1.595 BFLOPs 63 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 64 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 65 res 62 13 x 13 x1024 -> 13 x 13 x1024 66 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 67 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 68 res 65 13 x 13 x1024 -> 13 x 13 x1024 69 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 70 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 71 res 68 13 x 13 x1024 -> 13 x 13 x1024 72 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 73 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 74 res 71 13 x 13 x1024 -> 13 x 13 x1024 75 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 76 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 77 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 78 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 79 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 0.177 BFLOPs 80 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 1.595 BFLOPs 81 conv 255 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 255 0.088 BFLOPs 82 yolo # small尺寸的特徵圖 13*13*(3*(5+80)) 83 route 79 84 conv 256 1 x 1 / 1 13 x 13 x 512 -> 13 x 13 x 256 0.044 BFLOPs 85 upsample 2x 13 x 13 x 256 -> 26 x 26 x 256 # 對當前特徵層進行上取樣 86 route 85 61 # concat 85和61層 起到特徵合併的作用 類似FPN的思想 87 conv 256 1 x 1 / 1 26 x 26 x 768 -> 26 x 26 x 256 0.266 BFLOPs 88 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 89 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 90 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 91 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 0.177 BFLOPs 92 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 1.595 BFLOPs 93 conv 255 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 255 0.177 BFLOPs 94 yolo # middle尺寸的特徵圖 26*26*(3*(5+80)) 95 route 91 96 conv 128 1 x 1 / 1 26 x 26 x 256 -> 26 x 26 x 128 0.044 BFLOPs 97 upsample 2x 26 x 26 x 128 -> 52 x 52 x 128 # 上取樣 98 route 97 36 # cocat 97和36層 99 conv 128 1 x 1 / 1 52 x 52 x 384 -> 52 x 52 x 128 0.266 BFLOPs 100 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 1.595 BFLOPs 101 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 0.177 BFLOPs 102 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 1.595 BFLOPs 103 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 0.177 BFLOPs 104 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 1.595 BFLOPs 105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs 106 yolo # large尺寸的特徵圖 52*52*(3*(5+80)) Loading weights from model/yolov3.weights...Done! data/dog.jpg: Predicted in 0.024054 seconds. # 1080T inference time # 影象中類別和置信度 dog: 99% truck: 92% bicycle: 99%

從darknet原始碼中理解yolov3 forward資料流動

  • yolov3 detect入口
//example/darknet.c main函式
} else if (0 == strcmp(argv[1], "detector")){
    run_detector(argc, argv);
    
//example/detector.c run_detector函式
if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen); // 根據系統引數配置網路輸入檔案資訊thresh=0.5, hier_thresh=0.5(看程式碼不知道這個引數是否用到,後面再分析吧),outfile=null fullscreen=0
  • yolov3 detect核心函式: test_detector
//example/detector.c test_detector函式
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
    /*將/data/coco.names裡面的label載入到names中*/
    list *options = read_data_cfg(datacfg); 
    char *name_list = option_find_str(options, "names", "data/names.list");
    char **names = get_labels(name_list);

    image **alphabet = load_alphabet(); // 將/data/label的影象載入到 image陣列中,darknet中最後展現在result中的label都是以影象的形式展現出來的,而不是用put_text到影象中的
    network *net = load_network(cfgfile, weightfile, 0); // 載入cfg和引數構建darknet network -> 稍後具體分析(1)
    set_batch_network(net, 1); // 將 network裡面layer的batch_size都設定為1
    srand(2222222);
    double time;
    char buff[256];
    char *input = buff;
    float nms=.45;
    while(1){
        if(filename){
            strncpy(input, filename, 256);
        } else {
            printf("Enter Image Path: ");
            fflush(stdout);
            input = fgets(input, 256, stdin);
            if(!input) return;
            strtok(input, "\n");
        }
        /*yolov3輸入的影象預處理:
            1.除以255歸一化 
            2.影象居中等比例縮放padding 127.5/255
            3.BGR2RGB
            4.NHWC2NCHW
            影象處理部分邏輯比較簡單,需要注意的主要是等比例縮放,在不使用opencv的情況下使用C影象庫stb_image,用影象w h c以及資料data初始化一個image結構體*/
        image im = load_image_color(input,0,0);
        image sized = letterbox_image(im, net->w, net->h);
        //image sized = resize_image(im, net->w, net->h);
        //image sized2 = resize_max(im, net->w);
        //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
        //resize_network(net, sized.w, sized.h);
        layer l = net->layers[net->n-1]; // 獲取最後一個yolo layer, 主要是為了獲取類別資訊吧,因為三個yolo layer的input size都不相同


        float *X = sized.data;
        time=what_time_is_it_now();
        network_predict(net, X); // 連續呼叫layer的forward做inference
        printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
        int nboxes = 0;
        detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes); //呼叫yolo_layer對三個output tensor進行分析 -> 稍後具體分析(2)
        //printf("%d\n", nboxes);
        //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
        if (nms) do_nms_sort(dets, nboxes, l.classes, nms); // 對三個層級的bbox做nms, nms的演算法思想不難但是沒有好好看過實現,關於darknet的nms還是需要理解一下 -> 分析完yolo_layer後簡單分析一下nms的實現 (3)
        draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes); // 把檢測到的目標展示出來, 第一次研究這個function的時候發現不是簡單的展示, 還做了一些小處理的 -> 稍後分析 (4)
        free_detections(dets, nboxes);
        if(outfile){
            save_image(im, outfile);
        }
        else{
            save_image(im, "predictions");
#ifdef OPENCV
            make_window("predictions", 512, 512, 0);
            show_image(im, "predictions", 0);
#endif
        }

        free_image(im);
        free_image(sized);
        if (filename) break;
    }
}
  • darknet網路模型的構建: yolov3模型cfg和引數載入
//src/network.c load_network函式
network *load_network(char *cfg, char *weights, int clear)
{
    network *net = parse_network_cfg(cfg); //將網路的cfg檔案引數化,即解析cfg配置檔案
    if(weights && weights[0] != 0){
        load_weights(net, weights); // 根據cfg構建的network按照layer的順序載入對一個的layer引數權重
    }
    if(clear) (*net->seen) = 0; // *net->seen 代表目前網路已經處理的影象數量 batch_num = net->batch * net->subdivisions 可以演算法網路已經處理的batch數量
    return net;
}

//src/parser.c parse_network_cfg函式
network *parse_network_cfg(char *filename)
{
    /*分析read_cfg: 個人理解darknet將cfg中每一個layer當做節點node其中val為section構建成一個連結串列list
    其中涉及的數結構有:
    typedef struct{
        char *type; //存放 layer name
        list *options; // 暫時不太清晰list成員的作用(存放layer的屬性欄位?)
    }section;
    
    typedef struct node{
        void *val; // 存放當前section
        struct node *next; 
        struct node *prev;
    } node;

    typedef struct list{
        int size; // 連結串列節點個數
        node *front;
        node *back;
    } list;
    */
    list *sections = read_cfg(filename);
    node *n = sections->front;
    if(!n) error("Config file has no sections");
    network *net = make_network(sections->size - 1); // 為構建網路分類記憶體 calloc (malloc並且初始化為0)
    net->gpu_index = gpu_index;
    size_params params;

    section *s = (section *)n->val;
    list *options = s->options;
    if(!is_network(s)) error("First section must be [net] or [network]");
    parse_net_options(options, net); // 初始化網路全域性引數

    params.h = net->h;
    params.w = net->w;
    params.c = net->c;
    params.inputs = net->inputs;
    params.batch = net->batch;
    params.time_steps = net->time_steps;
    params.net = net;

    size_t workspace_size = 0;
    n = n->next;
    int count = 0;
    free_section(s);
    fprintf(stderr, "layer     filters    size              input                output\n");
    while(n){ // 初始化每一層的引數,這部分內容比較多,就不在yolov3這個模組展開了,如果有必要的話會單獨對網路引數和layer引數的載入進行學習和分析
        params.index = count;
        fprintf(stderr, "%5d ", count);
        s = (section *)n->val;
        options = s->options;
        layer l = {0};
        LAYER_TYPE lt = string_to_layer_type(s->type);
        if(lt == CONVOLUTIONAL){
            l = parse_convolutional(options, params);
        }else if(lt == DECONVOLUTIONAL){
            l = parse_deconvolutional(options, params);
        }else if(lt == LOCAL){
            l = parse_local(options, params);
        }else if(lt == ACTIVE){
            l = parse_activation(options, params);
        }else if(lt == LOGXENT){
            l = parse_logistic(options, params);
        }else if(lt == L2NORM){
            l = parse_l2norm(options, params);
        }else if(lt == RNN){
            l = parse_rnn(options, params);
        }else if(lt == GRU){
            l = parse_gru(options, params);
        }else if (lt == LSTM) {
            l = parse_lstm(options, params);
        }else if(lt == CRNN){
            l = parse_crnn(options, params);
        }else if(lt == CONNECTED){
            l = parse_connected(options, params);
        }else if(lt == CROP){
            l = parse_crop(options, params);
        }else if(lt == COST){
            l = parse_cost(options, params);
        }else if(lt == REGION){
            l = parse_region(options, params);
        }else if(lt == YOLO){ // yolov3獨有的yolo_layer
            l = parse_yolo(options, params);
        }else if(lt == ISEG){
            l = parse_iseg(options, params);
        }else if(lt == DETECTION){
            l = parse_detection(options, params);
        }else if(lt == SOFTMAX){
            l = parse_softmax(options, params);
            net->hierarchy = l.softmax_tree;
        }else if(lt == NORMALIZATION){
            l = parse_normalization(options, params);
        }else if(lt == BATCHNORM){
            l = parse_batchnorm(options, params);
        }else if(lt == MAXPOOL){
            l = parse_maxpool(options, params);
        }else if(lt == REORG){
            l = parse_reorg(options, params);
        }else if(lt == AVGPOOL){
            l = parse_avgpool(options, params);
        }else if(lt == ROUTE){
            l = parse_route(options, params, net);
        }else if(lt == UPSAMPLE){
            l = parse_upsample(options, params, net);
        }else if(lt == SHORTCUT){
            l = parse_shortcut(options, params, net);
        }else if(lt == DROPOUT){
            l = parse_dropout(options, params);
            l.output = net->layers[count-1].output;
            l.delta = net->layers[count-1].delta;
#ifdef GPU
            l.output_gpu = net->layers[count-1].output_gpu;
            l.delta_gpu = net->layers[count-1].delta_gpu;
#endif
        }else{
            fprintf(stderr, "Type not recognized: %s\n", s->type);
        }
        l.clip = net->clip;
        l.truth = option_find_int_quiet(options, "truth", 0);
        l.onlyforward = option_find_int_quiet(options, "onlyforward", 0);
        l.stopbackward = option_find_int_quiet(options, "stopbackward", 0);
        l.dontsave = option_find_int_quiet(options, "dontsave", 0);
        l.dontload = option_find_int_quiet(options, "dontload", 0);
        l.numload = option_find_int_quiet(options, "numload", 0);
        l.dontloadscales = option_find_int_quiet(options, "dontloadscales", 0);
        l.learning_rate_scale = option_find_float_quiet(options, "learning_rate", 1);
        l.smooth = option_find_float_quiet(options, "smooth", 0);
        option_unused(options);
        net->layers[count] = l;
        if (l.workspace_size > workspace_size) workspace_size = l.workspace_size;
        free_section(s);
        n = n->next;
        ++count;
        if(n){ // 這部分將連線的兩個層之間的輸入輸出shape統一
            params.h = l.out_h;
            params.w = l.out_w;
            params.c = l.out_c;
            params.inputs = l.outputs;
        }
    }
    free_list(sections);
    layer out = get_network_output_layer(net); //返回網路的輸出layer
    net->outputs = out.outputs;
    net
            
           

相關推薦

darknet原始碼學習 (1) :yolov3 推理過程

最近專案中會頻繁用到yolov3這個目標檢測演算法框架,由於其在速度和精度尤其是小物體檢測的能力上都比較突出所以目前應用面很廣泛,在應用yolov3的過程中經常會遇到一些演算法上的疑點,由於之前沒有好好學習過darknet這個輕量級DL演算法框架所以決定從yolov3入手理清一些

Yolo系列學習1-Yolov3訓練福彩快三平臺出租自己的數據

平臺 sprint rectangle 修改文件 ted issues printf res idt 目的:福彩快三平臺出租 haozbbs.com Q1446595067 實現利用yolov3訓練自己的數據集(voc格式) 方法: 1)構建VOC數據集 將你手中的數據

struts1原始碼學習1

ActionServlet初始化方法init public class ActionServlet extends HttpServlet //servlet初始化 public void init() throws ServletException { final

Yolo系列學習1-Yolov3訓練自己的資料

前提: 目的: 實現利用yolov3訓練自己的資料集(voc格式) 方法: 1)構建VOC資料集 將你手中的資料集的標註txt修改成voc格式的txt,voc格式如下: 000002.jpg car 44 28 132 121 000003.jpg

spring原始碼學習(1) --BeanDefinition學習

BeanDefinition BeanDefinition作為定義springBean檔案中bean的介面,可以說是bean的抽象資料結構,它包括屬性引數,構造器引數,以及其他具體的引數。 <bean id="person" class="com.de

Redis原始碼學習(1):adlist

redis中的adlist是一個簡單的無環雙向連結串列,主要邏輯在這兩個檔案中實現: adlist.h adlist.c 結構也很簡單,資料結構中最基本的結構。 新增節點程式碼: if (after) { node->prev =

結合redis設計與實現的redis原始碼學習-1-記憶體分配(zmalloc)

在進入公司後的第一個任務就是使用redis的快取功能實現伺服器的雲託管功能,在瞭解了大致需求後,依靠之前對redis的瞭解封裝了常用的redis命令,並使用單例的連線池來維護與redis的連線,使用連線池來獲取redis的連線物件,依靠這些功能基本可以實現要求的

TensorFlow原始碼學習--1 Session API reference

學習TensorFlow原始碼,先把API文件扒出來研究一下整體結構: 一下是文件內容的整理,簡單翻譯一下 原文地址:http://www.tcvpr.com/archives/181 TensorFlow C++ Session API reference

linux TCP傳送原始碼學習(1)--tcp_sendmsg

一、tcp_sendmsg()函式分析: int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,         size_t size) {

Darknet 原始碼學習和非常詳細的中文註釋(絕對經典)

https://pjreddie.com/darknet/ 用於人臉表情端到端系統的重訓練 附錄1: darknet深度學習框架原始碼分析:詳細中文註釋,涵蓋框架原理與實現語法分析 https://github.com/hgpvision/da

HBase原始碼學習 客戶端scan過程

申明:以下程式碼均來自HBase-1.0.1.1 HTable tb = new HTable(conf,"test"); Scan scan = new Scan(); scan.addColumn(("colfam").getBytes(),("col

Asp.NetCore原始碼學習[1-2]:配置[Option]

Asp.NetCore原始碼學習[1-2]:配置[Option] 在上一篇文章中,我們知道了可以通過IConfiguration訪問到注入的ConfigurationRoot,但是這樣只能通過索引器IConfiguration["配置名"]訪問配置。這篇文章將一下如何將IConfiguration對映到

Hive原始碼分析(1)——HiveServer2啟動過程

1.想了解HiveServer2的啟動過程,則需要找到啟動HiveServer2的入口,hive服務的啟動命令為hive --service HiveServer2,通過分析$HIVE_HOME/bin下hive指令碼可知,執行hive --service HiveServer2後正真呼叫的是$HIVE_HO

spring原始碼學習之路---深度分析IOC容器初始化過程(三)

分析FileSystemXmlApplicationContext的建構函式,到底都做了什麼,導致IOC容器初始化成功。 public FileSystemXmlApplicationContext(String[] configLocations, boolean ref

spring原始碼學習(5.1.0版本)——Bean的初始化(中)

目錄   前言 createBean 有自定義TargetSource代理類的生成 resolveBeforeInstantiation applyBeanPostProcessorsBeforeInstantiation postProcessBeforeIn

spring原始碼學習(5.1.0版本)——Bean的初始化(上)

目錄   前言 源頭 preInstantiateSingletons方法 getBean(String beanName) doGetBean getObjectForBeanInstance getObjectFromFactoryBean doGe

Hadoop-0.20.2原始碼學習1)——原始碼初窺

參考: JeffreyZhou的部落格園 《Hadoop權威指南》第四版 0. 為什麼選擇0.20.2版本 前面學習搭建的Hadoop版本是2.7.6,可是這裡為什麼要學習0.20.2這麼老的版本呢?

區塊鏈學習1.5-比特幣原始碼學習-比特幣網路

本篇文章有部分內容直接出自《Mastering Bitcoin》 比特幣網路層主要是由 P2P網路,傳播機制,驗證機制三部分組成。 白皮書關於network的內容回顧一下: The steps to run the network are as follows:

區塊鏈學習1.4-比特幣原始碼學習-比特幣基礎

1.3就已經提到區塊鏈的四大技術組合,我認為還是有必要了解背後的原理的。下面做一個簡要的介紹。 一. 區塊鏈資料結構和數字簽名演算法 1.資料結構Merkel樹 說到merkle樹就不得不談到交易,merkle樹就是用於存放交易的 資料結構。如下圖: 它是一個雜湊二叉樹,雜湊的

View的工作原理之Measure過程原始碼學習(四)

       上一篇文章,學習了ViewGroup和View的measure流程。文章最後講到,本文將會學習ViewGroup和普通View的onMeasure方法的工作。        因為ViewGroup是