mmdetection最小復(fù)刻版(四):獨家yolo轉(zhuǎn)化內(nèi)幕——#mmdetection學習

      網(wǎng)友投稿 961 2022-05-30

      本文轉(zhuǎn)載自

      https://www.zybuluo.com/huanghaian/note/1744915

      github: https://github.com/hhaAndroid/mmdetection-mini

      歡迎star

      部分內(nèi)容有刪改

      1 darknet系列轉(zhuǎn)化過程

      由于yolov2比較老了,性能和速度都無法和yolov3、tiny-yolov3相比,故這里不包括yolov2,具體是yolov3/yolov4以及tiny-yolo3/tiny-yolov4,一共4個模型。

      剛開始我對darknet不熟悉,只是看cfg而已,darknet源碼是完全沒看過(當然現(xiàn)在也沒有看過)。如果你想找pytorch版本的yolo系列,github當然也是不計其數(shù)。搜索yolov3,帶有pytorch的star最多的是eriklindernoren/PyTorch-YOLOv3庫,但打開model.py,如下所示:

      很明顯,他也是直接解析yolov3.cfg來構(gòu)建模型的。而前面說過我比較不喜歡這個做法,所以這種方式我直接就放棄了。

      然后我找到了騰訊優(yōu)圖的目標檢測庫,在當時他就已經(jīng)實現(xiàn)了yolov2和yolov3系列了,他的模型構(gòu)建就是標準的pytorch寫法,通俗易懂,所以我一直沿用了這種做法。

      通過netron可視化網(wǎng)絡(luò)結(jié)構(gòu),可以非常容易看出網(wǎng)絡(luò)結(jié)構(gòu)以及檢查自己寫的代碼是否正確,起到了極大的幫助。

      我首先把ObjectDetection-OneStageDet代碼看了一遍并且跑了下,基本上了解了細節(jié)。考慮到y(tǒng)olov3模型太大了,不好搞,所以先拿tiny-yolov3開刀,一旦整個流程通了,那么新增yolov3那還不是手到擒來。

      1.1 第一步:構(gòu)建模型

      構(gòu)建tiny-yolov3模型,那實在是太簡單了,因為結(jié)構(gòu)非常簡單,為了快速實現(xiàn),我們可以模仿騰訊優(yōu)圖做法,如下所示,地址為:

      https://github.com/Tencent/ObjectDetection-OneStageDet/blob/master/vedanet/network/backbone/_tiny_yolov3.py:

      我開始也是按照這個寫法寫的,對應(yīng)框架代碼里面的mmdet/models/backbones/rr_tiny_yolov3_backbone.py。tiny-yolov3是兩個輸出尺度,而不是三個,下采樣率為32和16。為了檢查代碼是否正確,我還使用了darknet的cfg文件進行可視化。

      顯示backbone已經(jīng)寫好了,下面就是構(gòu)建head了。head部分也非常簡單,就是對stride=32的分支采用上采樣層然后和stride=16的特征層進行concat操作即可:

      1.2 第二步:權(quán)重轉(zhuǎn)化

      構(gòu)建模型是非常容易的,可以直接抄騰訊優(yōu)圖的代碼,但是權(quán)重轉(zhuǎn)化就沒那么容易了,需要自己搞定。要將tiny-yolov3權(quán)重轉(zhuǎn)化到mmdetection-mini中,需要做以下幾件事情:

      (1) 熟悉darknet權(quán)重保存格式.weights

      (2) 權(quán)重轉(zhuǎn)化為pytorch結(jié)構(gòu)

      對于darknet保存的weigts的格式解析,在網(wǎng)上有很多,我梳理了下大概就理解了。對應(yīng)代碼在mmdet/models/utils/brick.py里面的WeightLoader類中。整個權(quán)重文件其實是一個numpy矩陣,前幾個字節(jié)是一些版本以及head信息,后面開始才是權(quán)重,其格式為:

      如果僅僅是卷積層,那么存儲格式是先bias,然后采用卷積權(quán)重參數(shù)

      如果是conv+bn+激活,那么存儲格式是先bn的bias,weights,然后采用卷積的bias,weights

      明白這兩個就可以了,解析時候必須要按照這個格式來讀取,否則后果非常嚴重,后面會細說。并且必須以conv+bn+激活的組合格式來解析,因為darkent的最小配置單位就是這個,如果你在pytorch構(gòu)建模型時候,把conv和bn分開寫,那么解析難度會增加非常多,注意看我的yolo系列的最小模型單位是Conv2dBatchLeaky或者Conv2,目的就是為了解析方便。

      理解了上面的流程,就大概知道咋弄了,但是為了轉(zhuǎn)換方便以及轉(zhuǎn)換本身就是僅僅運行一次即可,不需要每次跑訓練都轉(zhuǎn)換一次,故我把tiny-yolov3在tools/darknet/tiny_yolov3.py里面copy了一遍,模型是一樣的,主要目的是:任何人先用這個腳本轉(zhuǎn)換一下模型,保存為mmdetection-mini能夠直接讀取的格式,然后在訓練和測試時候就直接用轉(zhuǎn)換后的模型即可,而不再需要讀取darknet原始weights權(quán)重。

      tools/darknet/tiny_yolov3.py里面采用了WeightLoader類來加載darknet權(quán)重:

      注意必須實現(xiàn)__modules_recurse方法,該方法的作用是遍歷復(fù)雜模型的每個子模塊,一定要遍歷到最小單位即conv+bn+激活或者conv,因為WeightLoader類的輸入必須是這兩個,否則無法解析。

      下載darknet的tiny-yolov3權(quán)重,替換tiny_yolov3.py的權(quán)重路徑,運行即可。

      打印信息如下,跑到這里,你需要檢查兩個部分:

      (1) 最后一行[8858734/8858734 weights],如果兩者不相等,說明你代碼寫錯了,因為權(quán)重居然沒有全部導(dǎo)入

      (2) 檢查Layer skipped的層是否是沒有參數(shù)的層,如果跳過了帶參數(shù)層,那么你一定寫錯了

      到目前為止,一切都搞定了,tiny-yolov3的所有權(quán)重全部導(dǎo)入了,美好的一比,如果你這樣認為,那你就打錯特錯了。

      還有一個收尾的工作要做,這里保存的pth模型和mmdetection-mini里面的算法雖然一樣,但是模型的key是不一樣的,為了能夠在mmdetection-mini中直接跑,你還需要替換key,對于tiny-yolov3來說,稍微比較簡單,如下:

      如果你想說我咋知道哪些key要替換?我的做法是:先去mmdetection-mini中把模型的所有key保存下來,然后和這里的key比對下就行了。工作雖然繁瑣一些,但是還是能接受的。

      到這里為止,保存的pth就可以真的在mmdetection-mini中跑起來了。美好的一比,如果你這樣認為,那你又打錯特錯了。

      1.3 第三步: 結(jié)果檢查

      前面都是準備工作,模型也得到了,最核心的是驗證你轉(zhuǎn)化的模型進行推理時候效果對不對,可以從單張圖預(yù)測可視化和驗證集計算mAP兩個方面驗證。

      先對單張圖進行推理,看下預(yù)測效果對不對吧。此時你可以發(fā)現(xiàn),基本上啥都預(yù)測不出來。這一步才是最要命的,搞了半天,各種確認,最后發(fā)現(xiàn)沒有效果?這個地方坑很多,我開始被坑了一天。出現(xiàn)這個原因的主要問題是還是對darknet框架不熟悉。下面我說下錯誤原因,有好幾個。

      (1) 模型構(gòu)建順序不能錯

      因為darknet里面的權(quán)重是沒有開始和結(jié)尾的numpy數(shù)組,你只能自己去切割數(shù)組,這就會帶來一個問題:假設(shè)原始模式是ConvBN_1+ConvBN_2,但是你在pytorch里面寫成了ConvBN_2+ConvBN_1,那么上面寫的整個過程都是不會報錯而且是正常的,但是實際上你寫錯了。

      例如你可以在tools/darknet/tiny-yolov3.py里面把:

      self.layer0 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in layer0]) self.head0 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in head0])

      順序調(diào)換,寫成:

      self.head0 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in head0]) self.layer0 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in layer0])

      就是這么簡單,你再跑腳本,打印也是完全正確的,但是實際你錯的非常嚴重,因為權(quán)重切割錯了。

      總的來說就是darkent里面的權(quán)重,保存順序完全按照cfg從上到下的順序,而不是網(wǎng)絡(luò)實際運行順序。如果你的解析順序不正確,那么即使網(wǎng)絡(luò)結(jié)構(gòu)寫對了,導(dǎo)入的權(quán)重也可能是錯誤的,因為不會報錯。

      (2) head模型順序不能錯

      要說明這個問題,tiny-yolov3說明不了問題,需要上大模型yolov3。yolov3是典型的darknet53+fpn+head的結(jié)構(gòu),一般我們在構(gòu)建模型時候,都是按照mmdetection-mini的做法來寫,例如head部分我們的寫法是:

      這種寫法本身沒有問題,但是對于導(dǎo)入darknet權(quán)重來說就不行了,因為yolov3里面的配置是:

      yolo層其實就是輸出層,他的三個輸出層其實不是放在最后,而是插在中間的,也就是說他的權(quán)重保存模式是backbone+卷積+輸出1+fpn融合+輸出2+fpn融合+輸出3,而是通常的backbone+fpn+輸出1+輸出2+輸出3。

      這種結(jié)構(gòu)導(dǎo)致我們的pytorch模型構(gòu)建瞬間復(fù)雜很多,大家仔細看tools/darknet/yolov3.py里面的代碼拆分非常細,原因就是如此:

      self.layers1 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in layer_list1]) self.head1 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in head_1]) self.layers2 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in layer_list2]) self.head2 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in head_2]) self.layers3 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in layer_list3]) self.head3 = nn.ModuleList([nn.Sequential(layer_dict) for layer_dict in head_3])

      順序是layers1+head1+layers2+head2+layers3+head3,任何一個順序都不能亂,否則都是錯誤的。

      (3) concat一定要注意順序

      即使前面的你都弄好了,你依然可能出錯,因為concat不管你是a+b,還是b+a,代碼都不會報錯,但是實際你錯了,這個要非常小心,一定要執(zhí)行檢查,不要concat順序反了。

      我開始沒有想到這個問題,在測試tiny-yolov3時候,總是發(fā)現(xiàn)有一層的預(yù)測是正確的,但是另一個層是錯誤的,當時想了很久都搞不懂,最后才想到可能是concat的問題,最后調(diào)換下順序就好了。

      這個問題出現(xiàn)的原因依然是沒有仔細研究cfg,參數(shù)如下:

      [route] layers = -1, 8

      這個就表示是將最后一層輸出和第8個輸出層結(jié)果進行concat,順序是[-1,8]。

      (4) tiny-yolov3里面MaxPool2d寫錯了

      這個問題也被坑了很久,注意是因為騰訊優(yōu)圖里面寫錯了(他現(xiàn)在也是錯誤的),我當時沒有想到會錯,所以一直沒有懷疑這個,查了大概1天才發(fā)現(xiàn),被坑的很慘。tiny-yolov3的cfg中maxpool有一個地方非常奇怪:

      注意看這兩個maxpool參數(shù),第一個maxpool是標準寫法,但是第二個maxpool的stride=1,而不是2,騰訊優(yōu)圖里面寫的是:

      ('11_max', nn.MaxPool2d(3, 1, 1)),

      這個明顯不是cfg里面的寫法。對于騰訊優(yōu)圖復(fù)現(xiàn)的來說,可能影響很小,因為他根本就沒有導(dǎo)入tiny-yolov3權(quán)重,所以改了下沒有問題,但是我就不能這么搞了。正確的寫法應(yīng)該是:

      ('10_zero_pad', nn.ZeroPad2d((0, 1, 0, 1))), ('11_max', nn.MaxPool2d(2, 1)),

      先pad,然后在maxpool。當時寫錯情況下預(yù)測現(xiàn)象是預(yù)測的bbox會存在偏移即預(yù)測正確,但是bbox偏掉了。我開始一直懷疑是我bbox還原代碼寫錯了,因為解碼錯誤也很出現(xiàn)Bbox偏移,最后才發(fā)現(xiàn)是這個參數(shù),好慘啊!

      這個坑,被坑了很久。

      (5) bn和激活參數(shù)設(shè)置不一致

      這個也要非常小心,一定要執(zhí)行看下bn和激活函數(shù)參數(shù)設(shè)置,否則影響很大。例如LeakyReLU的激活參數(shù)是0.1,而不是默認的0.01。你最好先檢查這個參數(shù)。

      (6) 圖片輸入處理流程不一致

      假設(shè)輸入是416x416,其測試流程是先進行pad為正方形,然后resize,但是需要注意其resize圖片是采用最近鄰,而框架中是線性插值。實際測試表明差距還是比較大,會導(dǎo)致一些框丟失,可能darknent訓練時候采用的是最近鄰。

      而且,我原來寫的代碼一直都是bgr輸入,測試時候發(fā)現(xiàn)效果總是不太對,后來才發(fā)現(xiàn)darknet網(wǎng)絡(luò)輸入是rgb,且直接除以255進行歸一化。

      注意:我在mmdetection-mini中測試依然用的是線性插值模式,沒有修改,而且數(shù)據(jù)前處理邏輯也沒有完全按照darknet來做,而且沿用mmdetection的處理邏輯,如果你是用權(quán)重來微調(diào),我認為影響很小的。

      以上就是全部坑了,我踩過的坑全部記錄下了,希望對大家有點幫助。下面針對各個模型說明下一些細節(jié)。

      2 yolov3特殊說明

      按照前面的流程可以轉(zhuǎn)換darknet中的所有模型。對于yolov3而言,有些細節(jié)說明下:

      2.1 __modules_recurse函數(shù)

      前面說過darknet權(quán)重的最小單位是ConvBnAct,故需要通過自己遞歸模塊,直到符合條件為止。故對于自定義模塊例如HeadBody,你需要在Yolov3類開頭定義custom_layers,然后在__modules_recurse遞歸。如果子模塊里面還包括非最小模塊,則內(nèi)部模塊也要加入,否則依然無法導(dǎo)入:

      custom_layers = (vn_layer.Stage, vn_layer.Stage.custom_layers, vn_layer.HeadBody, vn_layer.Transition, vn_layer.Head)

      例如上面的Stage模塊,內(nèi)部還有定制模塊StageBlock,那么也要加入。

      如果你不加入,那么運行時候會看到很多conv層都被跳過了。

      并且由于yolov3的head寫法,導(dǎo)致在模型的key替換時候會稍微麻煩一些,這種問題只需要細心點就可以解決,不難。

      對于我來說,只要搞定了tiny-yolov3和yolov3,那么其余模型都可以很快就轉(zhuǎn)換成功。

      3 tiny-yolov4特殊說明

      在tiny-yolov4中引入了一種新的配置:

      [route] layers=-1 groups=2 group_id=1

      groups=2表示將該特征圖平均分成兩組,group_id=1表示取第1組特征。實際上就是:

      x0 = x[:, channel // 2:, ...]

      其他地方就沒啥要注意的了,按照cfg可視化寫就行。

      4 轉(zhuǎn)化性能說明

      對應(yīng)的文檔在docs/model_zoo.md中,這里寫下詳情。

      在給出指標前,有一個地方一定要理清楚,否則各種指標你會看起來很懵。

      coco數(shù)據(jù)集劃分方法有兩種:coco2014和coco2017,在https://cocodataset.org/#download里面有說明,大概就是

      MSCOCO2014數(shù)據(jù)集:

      訓練集: 82783張,13.5GB, 驗證集:40504張,6.6GB,共計123287張

      MSCOCO2017數(shù)據(jù)集:

      訓練集:118287張,19.3GB,驗證集: 5000張,1814.7M,共計123287張

      數(shù)據(jù)集應(yīng)該還是那些,但是劃分原則改變了。在coco2014中驗證集數(shù)據(jù)太多了,很多大佬做實驗時候其實不是這樣搞的,參考retinanet論文里面的說法:訓練集通常都是train2014+val2014-minival2014,也就是從val2014中隨機挑選5000張圖片構(gòu)成minival數(shù)據(jù)集,其余數(shù)據(jù)集全部當做訓練集。論文中貼的指標都是test_dev2014數(shù)據(jù),是本地生成json,然后發(fā)送給coco官方服務(wù)器得到mAP值,本地是沒有l(wèi)abel的。

      基于大家通常的做法,coco官方在2017年開始也采用了這種切分方式,也就是原來是大家各種隨機切分val得到minival,但是現(xiàn)在官方統(tǒng)一了minival,讓大家數(shù)據(jù)完全一樣,對比更加公平。故在2017年開始,劃分就變成train2014+val2014-minival2014=train2017,minival2014=val2017,測試集應(yīng)該沒咋變。

      也就是是說yolov3論文中的指標都是test_dev2014的結(jié)果,訓練是train2014+val2014-minival2014。一定要注意minival2014是各自隨機切分的,也就是minival2014雖然圖片數(shù)和val2017一樣,但是有可能val2017的數(shù)據(jù)有部分數(shù)據(jù)其實在train2014+val2014-minival2014中。

      4.1 測試前置說明

      (1) mmdetection中貼的指標都是在val2017上面測試的,而darknet中提供的指標都是test_dev2014上面的,不具有非常強的對比性,因為val2017可能出現(xiàn)在darknet的訓練集中,而且val數(shù)據(jù)集一般會比test_dev簡單一些

      (2) mmdetection中數(shù)據(jù)處理流程還是沿用mmdetection的,而不是darknet一樣的處理流程,會有點點差距,而且閾值也不太一樣,但是對于最終結(jié)果不會差距很大或者說沒有關(guān)系(只要證明我的代碼沒有問題就行)。在后面的yolov5中我進行了深入分析,可以保證處理流程完全一致,這里就暫時不寫了。

      4.2 yolov3指標

      權(quán)重下載鏈接: https://github.com/AlexeyAB/darknet

      對應(yīng)配置: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3.cfg

      darknet(test_dev2014): 416x416 55.3% mAP@0.5 (31.0 mAP@0.5:0.95) - 66? FPS - 65.9 BFlops - 236 MB

      darknet(val2017): 416x416 65.9 mAP@0.5

      mmdetection(val2017): 416x416 66.8 mAP@0.5 (37.4 mAP@0.5:0.95) -248 MB

      darknet(test_dev2014)是官方論文指標,而darknet(val2017)是我直接用darknet框架測試val2017數(shù)據(jù)得到的指標,而mmdetection(val2017)是mini框架轉(zhuǎn)換后進行測試的指標。可以發(fā)現(xiàn)指標會更高一些,原因應(yīng)該是后處理閾值不一樣。

      這里就可以完全確定代碼肯定沒有問題了。

      4.3 yolov4

      權(quán)重下載鏈接: https://github.com/AlexeyAB/darknet

      對應(yīng)配置: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4.cfg

      darknet(test_dev2014): 416x416 62.8% mAP@0.5 (41.2% AP@0.5:0.95) - 55? FPS / 96(V) FPS - 60.1 BFlops 245 MB

      darknet(test_dev2014): 608x608 65.7% mAP@0.5 (43.5% AP@0.5:0.95) - 55? FPS / 96(V) FPS - 60.1 BFlops 245 MB

      mmdetection(val2017): 416x416 65.7% mAP@0.5 (41.7% AP@0.5:0.95) -257. MB

      mmdetection(val2017): 608x608 72.9% mAP@0.5 (48.1% AP@0.5:0.95) -257.7 MB

      注意: yolov4的anchor尺寸變了,我開始沒有發(fā)現(xiàn)導(dǎo)致測試mAP比較低,不同于yolov3,下載的權(quán)重是608x608訓練過的,測試用了兩種尺度而已

      4.4 tiny-yolov3

      權(quán)重下載鏈接: https://github.com/AlexeyAB/darknet

      對應(yīng)配置: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3-tiny.cfg

      darknet(test_dev2014): 416x416 33.1% mAP@0.5 - 345? FPS - 5.6 BFlops - 33.7 MB

      mmdetection最小復(fù)刻版(四):獨家yolo轉(zhuǎn)化內(nèi)幕——#mmdetection學習

      mmdetection(val2017): 416x416 36.5% mAP@0.5 -35.4 MB

      注意: yolov3-tiny.cfg中最后一個[yolo]節(jié)點,mask應(yīng)該是 1,2,3,而不是github里面的0,1,2

      4.5 tiny-yolov4

      權(quán)重下載鏈接: https://github.com/AlexeyAB/darknet

      對應(yīng)配置: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4-tiny.cfg

      darknet(test_dev2014): 416x416 40.2% mAP@0.5 - 371(1080Ti) FPS / 330(RTX2070) FPS - 6.9 BFlops - 23.1 MB

      mmdetection(val2017): 416x416 37.9% mAP@0.5 (19.2 mAP@0.5:0.95) -24.3 MB

      注意:更低的原因應(yīng)該是該配置里面有一個scale_x_y = 1.05參數(shù)不一樣,目前沒有利用到,我后面會解決這個問題。

      由于我的測試集是val2017,而不是test_dev2014,所以我提供的mAP會高一些,這是非常正常的。

      5 總結(jié)

      我提供的代碼應(yīng)該沒有大問題,但是有些細節(jié)可能有點問題,我后面會慢慢完善。希望大家看完這篇文章能夠?qū)W會:

      (1) darknet的cfg可視化方式;權(quán)重存儲方式

      (2) 對于以后任何darknet模型,都可以僅僅用1個小時就轉(zhuǎn)換到mmdetection中進行微調(diào)訓練

      (3) darknet訓練、測試方式和mmdetection中的區(qū)別

      我后面會去研究darknet的具體實現(xiàn)過程,爭取理解的更加透徹。還有一些不完善的地方,歡迎提出改進意見。下一篇對yolov5轉(zhuǎn)換過程進行深度講解。

      6 附加

      其實后續(xù)我還想完成一件事情即模仿大部分pytorch復(fù)現(xiàn)yolo系列寫法,提供一個通用的pytorch模型,可以直接加載darknet權(quán)重,輸入也是cfg模式,內(nèi)部自動解析cfg來構(gòu)建模型。這樣就可以實現(xiàn)兩種目的:

      (1) 如果想了解所有模型細節(jié),可以按照原來的流程,先轉(zhuǎn)化再導(dǎo)入pth權(quán)重

      (2) 如果想快速將darknet模型應(yīng)用到mmdetection中,而不寫一行代碼,那么就可以用這個通用的pytorch模型了。

      但是其需要做如下工作:

      (1) 這個pytorch模型要能夠適應(yīng)所有cfg,也就是必須實時了解darknet里面有沒有新增一些騷操作,這邊也要實時兼容

      (2) 對于微調(diào)訓練,也要支持不同輸出層的權(quán)重不導(dǎo)入

      圖像處理 深度學習 神經(jīng)網(wǎng)絡(luò)

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:第十屆藍橋杯 2019年國賽真題(Java 大學C組)(第十屆藍橋杯scratch真題和答案)
      下一篇:excel表格里的圖片復(fù)制出來的方法(excel表格里的圖片怎么復(fù)制出來)
      相關(guān)文章
      亚洲日产乱码一二三区别| 亚洲国产一区在线观看| 亚洲影院天堂中文av色| 亚洲天堂电影在线观看| 亚洲日韩乱码中文无码蜜桃臀| 亚洲一区中文字幕久久| 无码久久精品国产亚洲Av影片| 国产av无码专区亚洲av果冻传媒| 亚洲精品无码你懂的网站| 亚洲精品专区在线观看| 国产亚洲AV夜间福利香蕉149| 色久悠悠婷婷综合在线亚洲| 亚洲中文字幕无码不卡电影| 国产AV无码专区亚洲AV手机麻豆| 亚洲乱码一区二区三区在线观看| 亚洲精品成人片在线播放 | 亚洲中文字幕无码永久在线| 亚洲综合另类小说色区| 亚洲精品成人片在线观看精品字幕| 国产av无码专区亚洲av果冻传媒| 亚洲精品国产精品乱码不99| 亚洲AV成人片色在线观看| 18亚洲男同志videos网站| 亚洲春色在线观看| 亚洲综合一区无码精品| 亚洲AV无码一区二区三区牲色| 国产精品无码亚洲精品2021| 亚洲精品视频在线观看你懂的| 日本亚洲国产一区二区三区| 国产成A人亚洲精V品无码性色 | 亚洲AV无码成人精品区天堂 | 青青青国产色视频在线观看国产亚洲欧洲国产综合 | 亚洲另类激情综合偷自拍| 久久久久亚洲AV无码麻豆| 亚洲成a人片7777| 亚洲五月综合缴情婷婷| 亚洲成AV人片高潮喷水| 亚洲精品无码永久在线观看 | 亚洲乱码无码永久不卡在线| 午夜亚洲AV日韩AV无码大全| 亚洲欧洲日产国码在线观看|