├── README.md ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── yolov5s.bin │ └── yolov5s.param │ ├── java │ └── com │ │ └── tencent │ │ └── yolov5ncnn │ │ ├── MainActivity.java │ │ └── YoloV5Ncnn.java │ ├── jni │ ├── CMakeLists.txt │ └── yolov5ncnn_jni.cpp │ └── res │ ├── layout │ └── main.xml │ └── values │ └── strings.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot.jpg └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # ncnn-android-yolov5 2 | 3 | The YOLOv5 object detection 4 | 5 | this is a sample ncnn android project, it depends on ncnn library only 6 | 7 | https://github.com/Tencent/ncnn 8 | 9 | ## how to build and run 10 | ### step1 11 | https://github.com/Tencent/ncnn/releases 12 | 13 | download ncnn-android-vulkan.zip or build ncnn for android yourself 14 | 15 | ### step2 16 | extract ncnn-android-vulkan.zip into app/src/main/jni or change the ncnn_DIR path to yours in app/src/main/jni/CMakeLists.txt 17 | 18 | ### step3 19 | open this project with Android Studio, build it and enjoy! 20 | 21 | ## screenshot 22 | ![](screenshot.jpg) 23 | 24 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "29.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.tencent.yolov5ncnn" 9 | archivesBaseName = "$applicationId" 10 | 11 | ndk { 12 | moduleName "ncnn" 13 | abiFilters "armeabi-v7a", "arm64-v8a" 14 | } 15 | minSdkVersion 24 16 | } 17 | 18 | externalNativeBuild { 19 | cmake { 20 | version "3.10.2" 21 | path file('src/main/jni/CMakeLists.txt') 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/assets/yolov5s.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihui/ncnn-android-yolov5/14e728f4e3819824fbf8bcce10607481a932ac09/app/src/main/assets/yolov5s.bin -------------------------------------------------------------------------------- /app/src/main/assets/yolov5s.param: -------------------------------------------------------------------------------- 1 | 7767517 2 | 192 216 3 | Input images 0 1 images 4 | YoloV5Focus focus 1 1 images 207 5 | Convolution Conv_41 1 1 207 208 0=32 1=3 4=1 5=1 6=3456 6 | HardSwish Div_49 1 1 208 216 0=1.666667e-01 7 | Convolution Conv_50 1 1 216 217 0=64 1=3 3=2 4=1 5=1 6=18432 8 | HardSwish Div_58 1 1 217 225 0=1.666667e-01 9 | Split splitncnn_0 1 2 225 225_splitncnn_0 225_splitncnn_1 10 | Convolution Conv_59 1 1 225_splitncnn_1 226 0=32 1=1 5=1 6=2048 11 | HardSwish Div_67 1 1 226 234 0=1.666667e-01 12 | Split splitncnn_1 1 2 234 234_splitncnn_0 234_splitncnn_1 13 | Convolution Conv_68 1 1 234_splitncnn_1 235 0=32 1=1 5=1 6=1024 14 | HardSwish Div_76 1 1 235 243 0=1.666667e-01 15 | Convolution Conv_77 1 1 243 244 0=32 1=3 4=1 5=1 6=9216 16 | HardSwish Div_85 1 1 244 252 0=1.666667e-01 17 | BinaryOp Add_86 2 1 234_splitncnn_0 252 253 18 | Convolution Conv_87 1 1 253 254 0=32 1=1 6=1024 19 | Convolution Conv_88 1 1 225_splitncnn_0 255 0=32 1=1 6=2048 20 | Concat Concat_89 2 1 254 255 256 21 | BatchNorm BatchNormalization_90 1 1 256 257 0=64 22 | ReLU LeakyRelu_91 1 1 257 258 0=1.000000e-01 23 | Convolution Conv_92 1 1 258 259 0=64 1=1 5=1 6=4096 24 | HardSwish Div_100 1 1 259 267 0=1.666667e-01 25 | Convolution Conv_101 1 1 267 268 0=128 1=3 3=2 4=1 5=1 6=73728 26 | HardSwish Div_109 1 1 268 276 0=1.666667e-01 27 | Split splitncnn_2 1 2 276 276_splitncnn_0 276_splitncnn_1 28 | Convolution Conv_110 1 1 276_splitncnn_1 277 0=64 1=1 5=1 6=8192 29 | HardSwish Div_118 1 1 277 285 0=1.666667e-01 30 | Split splitncnn_3 1 2 285 285_splitncnn_0 285_splitncnn_1 31 | Convolution Conv_119 1 1 285_splitncnn_1 286 0=64 1=1 5=1 6=4096 32 | HardSwish Div_127 1 1 286 294 0=1.666667e-01 33 | Convolution Conv_128 1 1 294 295 0=64 1=3 4=1 5=1 6=36864 34 | HardSwish Div_136 1 1 295 303 0=1.666667e-01 35 | BinaryOp Add_137 2 1 285_splitncnn_0 303 304 36 | Split splitncnn_4 1 2 304 304_splitncnn_0 304_splitncnn_1 37 | Convolution Conv_138 1 1 304_splitncnn_1 305 0=64 1=1 5=1 6=4096 38 | HardSwish Div_146 1 1 305 313 0=1.666667e-01 39 | Convolution Conv_147 1 1 313 314 0=64 1=3 4=1 5=1 6=36864 40 | HardSwish Div_155 1 1 314 322 0=1.666667e-01 41 | BinaryOp Add_156 2 1 304_splitncnn_0 322 323 42 | Split splitncnn_5 1 2 323 323_splitncnn_0 323_splitncnn_1 43 | Convolution Conv_157 1 1 323_splitncnn_1 324 0=64 1=1 5=1 6=4096 44 | HardSwish Div_165 1 1 324 332 0=1.666667e-01 45 | Convolution Conv_166 1 1 332 333 0=64 1=3 4=1 5=1 6=36864 46 | HardSwish Div_174 1 1 333 341 0=1.666667e-01 47 | BinaryOp Add_175 2 1 323_splitncnn_0 341 342 48 | Convolution Conv_176 1 1 342 343 0=64 1=1 6=4096 49 | Convolution Conv_177 1 1 276_splitncnn_0 344 0=64 1=1 6=8192 50 | Concat Concat_178 2 1 343 344 345 51 | BatchNorm BatchNormalization_179 1 1 345 346 0=128 52 | ReLU LeakyRelu_180 1 1 346 347 0=1.000000e-01 53 | Convolution Conv_181 1 1 347 348 0=128 1=1 5=1 6=16384 54 | HardSwish Div_189 1 1 348 356 0=1.666667e-01 55 | Split splitncnn_6 1 2 356 356_splitncnn_0 356_splitncnn_1 56 | Convolution Conv_190 1 1 356_splitncnn_1 357 0=256 1=3 3=2 4=1 5=1 6=294912 57 | HardSwish Div_198 1 1 357 365 0=1.666667e-01 58 | Split splitncnn_7 1 2 365 365_splitncnn_0 365_splitncnn_1 59 | Convolution Conv_199 1 1 365_splitncnn_1 366 0=128 1=1 5=1 6=32768 60 | HardSwish Div_207 1 1 366 374 0=1.666667e-01 61 | Split splitncnn_8 1 2 374 374_splitncnn_0 374_splitncnn_1 62 | Convolution Conv_208 1 1 374_splitncnn_1 375 0=128 1=1 5=1 6=16384 63 | HardSwish Div_216 1 1 375 383 0=1.666667e-01 64 | Convolution Conv_217 1 1 383 384 0=128 1=3 4=1 5=1 6=147456 65 | HardSwish Div_225 1 1 384 392 0=1.666667e-01 66 | BinaryOp Add_226 2 1 374_splitncnn_0 392 393 67 | Split splitncnn_9 1 2 393 393_splitncnn_0 393_splitncnn_1 68 | Convolution Conv_227 1 1 393_splitncnn_1 394 0=128 1=1 5=1 6=16384 69 | HardSwish Div_235 1 1 394 402 0=1.666667e-01 70 | Convolution Conv_236 1 1 402 403 0=128 1=3 4=1 5=1 6=147456 71 | HardSwish Div_244 1 1 403 411 0=1.666667e-01 72 | BinaryOp Add_245 2 1 393_splitncnn_0 411 412 73 | Split splitncnn_10 1 2 412 412_splitncnn_0 412_splitncnn_1 74 | Convolution Conv_246 1 1 412_splitncnn_1 413 0=128 1=1 5=1 6=16384 75 | HardSwish Div_254 1 1 413 421 0=1.666667e-01 76 | Convolution Conv_255 1 1 421 422 0=128 1=3 4=1 5=1 6=147456 77 | HardSwish Div_263 1 1 422 430 0=1.666667e-01 78 | BinaryOp Add_264 2 1 412_splitncnn_0 430 431 79 | Convolution Conv_265 1 1 431 432 0=128 1=1 6=16384 80 | Convolution Conv_266 1 1 365_splitncnn_0 433 0=128 1=1 6=32768 81 | Concat Concat_267 2 1 432 433 434 82 | BatchNorm BatchNormalization_268 1 1 434 435 0=256 83 | ReLU LeakyRelu_269 1 1 435 436 0=1.000000e-01 84 | Convolution Conv_270 1 1 436 437 0=256 1=1 5=1 6=65536 85 | HardSwish Div_278 1 1 437 445 0=1.666667e-01 86 | Split splitncnn_11 1 2 445 445_splitncnn_0 445_splitncnn_1 87 | Convolution Conv_279 1 1 445_splitncnn_1 446 0=512 1=3 3=2 4=1 5=1 6=1179648 88 | HardSwish Div_287 1 1 446 454 0=1.666667e-01 89 | Convolution Conv_288 1 1 454 455 0=256 1=1 5=1 6=131072 90 | HardSwish Div_296 1 1 455 463 0=1.666667e-01 91 | Split splitncnn_12 1 4 463 463_splitncnn_0 463_splitncnn_1 463_splitncnn_2 463_splitncnn_3 92 | Pooling MaxPool_297 1 1 463_splitncnn_3 464 1=5 3=2 5=1 93 | Pooling MaxPool_298 1 1 463_splitncnn_2 465 1=9 3=4 5=1 94 | Pooling MaxPool_299 1 1 463_splitncnn_1 466 1=13 3=6 5=1 95 | Concat Concat_300 4 1 463_splitncnn_0 464 465 466 467 96 | Convolution Conv_301 1 1 467 468 0=512 1=1 5=1 6=524288 97 | HardSwish Div_309 1 1 468 476 0=1.666667e-01 98 | Split splitncnn_13 1 2 476 476_splitncnn_0 476_splitncnn_1 99 | Convolution Conv_310 1 1 476_splitncnn_1 477 0=256 1=1 5=1 6=131072 100 | HardSwish Div_318 1 1 477 485 0=1.666667e-01 101 | Convolution Conv_319 1 1 485 486 0=256 1=1 5=1 6=65536 102 | HardSwish Div_327 1 1 486 494 0=1.666667e-01 103 | Convolution Conv_328 1 1 494 495 0=256 1=3 4=1 5=1 6=589824 104 | HardSwish Div_336 1 1 495 503 0=1.666667e-01 105 | Convolution Conv_337 1 1 503 504 0=256 1=1 6=65536 106 | Convolution Conv_338 1 1 476_splitncnn_0 505 0=256 1=1 6=131072 107 | Concat Concat_339 2 1 504 505 506 108 | BatchNorm BatchNormalization_340 1 1 506 507 0=512 109 | ReLU LeakyRelu_341 1 1 507 508 0=1.000000e-01 110 | Convolution Conv_342 1 1 508 509 0=512 1=1 5=1 6=262144 111 | HardSwish Div_350 1 1 509 517 0=1.666667e-01 112 | Convolution Conv_351 1 1 517 518 0=256 1=1 5=1 6=131072 113 | HardSwish Div_359 1 1 518 526 0=1.666667e-01 114 | Split splitncnn_14 1 2 526 526_splitncnn_0 526_splitncnn_1 115 | Interp Resize_361 1 1 526_splitncnn_1 536 0=1 1=2.000000e+00 2=2.000000e+00 116 | Concat Concat_362 2 1 536 445_splitncnn_0 537 117 | Split splitncnn_15 1 2 537 537_splitncnn_0 537_splitncnn_1 118 | Convolution Conv_363 1 1 537_splitncnn_1 538 0=128 1=1 5=1 6=65536 119 | HardSwish Div_371 1 1 538 546 0=1.666667e-01 120 | Convolution Conv_372 1 1 546 547 0=128 1=1 5=1 6=16384 121 | HardSwish Div_380 1 1 547 555 0=1.666667e-01 122 | Convolution Conv_381 1 1 555 556 0=128 1=3 4=1 5=1 6=147456 123 | HardSwish Div_389 1 1 556 564 0=1.666667e-01 124 | Convolution Conv_390 1 1 564 565 0=128 1=1 6=16384 125 | Convolution Conv_391 1 1 537_splitncnn_0 566 0=128 1=1 6=65536 126 | Concat Concat_392 2 1 565 566 567 127 | BatchNorm BatchNormalization_393 1 1 567 568 0=256 128 | ReLU LeakyRelu_394 1 1 568 569 0=1.000000e-01 129 | Convolution Conv_395 1 1 569 570 0=256 1=1 5=1 6=65536 130 | HardSwish Div_403 1 1 570 578 0=1.666667e-01 131 | Convolution Conv_404 1 1 578 579 0=128 1=1 5=1 6=32768 132 | HardSwish Div_412 1 1 579 587 0=1.666667e-01 133 | Split splitncnn_16 1 2 587 587_splitncnn_0 587_splitncnn_1 134 | Interp Resize_414 1 1 587_splitncnn_1 597 0=1 1=2.000000e+00 2=2.000000e+00 135 | Concat Concat_415 2 1 597 356_splitncnn_0 598 136 | Split splitncnn_17 1 2 598 598_splitncnn_0 598_splitncnn_1 137 | Convolution Conv_416 1 1 598_splitncnn_1 599 0=64 1=1 5=1 6=16384 138 | HardSwish Div_424 1 1 599 607 0=1.666667e-01 139 | Convolution Conv_425 1 1 607 608 0=64 1=1 5=1 6=4096 140 | HardSwish Div_433 1 1 608 616 0=1.666667e-01 141 | Convolution Conv_434 1 1 616 617 0=64 1=3 4=1 5=1 6=36864 142 | HardSwish Div_442 1 1 617 625 0=1.666667e-01 143 | Convolution Conv_443 1 1 625 626 0=64 1=1 6=4096 144 | Convolution Conv_444 1 1 598_splitncnn_0 627 0=64 1=1 6=16384 145 | Concat Concat_445 2 1 626 627 628 146 | BatchNorm BatchNormalization_446 1 1 628 629 0=128 147 | ReLU LeakyRelu_447 1 1 629 630 0=1.000000e-01 148 | Convolution Conv_448 1 1 630 631 0=128 1=1 5=1 6=16384 149 | HardSwish Div_456 1 1 631 639 0=1.666667e-01 150 | Split splitncnn_18 1 2 639 639_splitncnn_0 639_splitncnn_1 151 | Convolution Conv_457 1 1 639_splitncnn_1 640 0=128 1=3 3=2 4=1 5=1 6=147456 152 | HardSwish Div_465 1 1 640 648 0=1.666667e-01 153 | Concat Concat_466 2 1 648 587_splitncnn_0 649 154 | Split splitncnn_19 1 2 649 649_splitncnn_0 649_splitncnn_1 155 | Convolution Conv_467 1 1 649_splitncnn_1 650 0=128 1=1 5=1 6=32768 156 | HardSwish Div_475 1 1 650 658 0=1.666667e-01 157 | Convolution Conv_476 1 1 658 659 0=128 1=1 5=1 6=16384 158 | HardSwish Div_484 1 1 659 667 0=1.666667e-01 159 | Convolution Conv_485 1 1 667 668 0=128 1=3 4=1 5=1 6=147456 160 | HardSwish Div_493 1 1 668 676 0=1.666667e-01 161 | Convolution Conv_494 1 1 676 677 0=128 1=1 6=16384 162 | Convolution Conv_495 1 1 649_splitncnn_0 678 0=128 1=1 6=32768 163 | Concat Concat_496 2 1 677 678 679 164 | BatchNorm BatchNormalization_497 1 1 679 680 0=256 165 | ReLU LeakyRelu_498 1 1 680 681 0=1.000000e-01 166 | Convolution Conv_499 1 1 681 682 0=256 1=1 5=1 6=65536 167 | HardSwish Div_507 1 1 682 690 0=1.666667e-01 168 | Split splitncnn_20 1 2 690 690_splitncnn_0 690_splitncnn_1 169 | Convolution Conv_508 1 1 690_splitncnn_1 691 0=256 1=3 3=2 4=1 5=1 6=589824 170 | HardSwish Div_516 1 1 691 699 0=1.666667e-01 171 | Concat Concat_517 2 1 699 526_splitncnn_0 700 172 | Split splitncnn_21 1 2 700 700_splitncnn_0 700_splitncnn_1 173 | Convolution Conv_518 1 1 700_splitncnn_1 701 0=256 1=1 5=1 6=131072 174 | HardSwish Div_526 1 1 701 709 0=1.666667e-01 175 | Convolution Conv_527 1 1 709 710 0=256 1=1 5=1 6=65536 176 | HardSwish Div_535 1 1 710 718 0=1.666667e-01 177 | Convolution Conv_536 1 1 718 719 0=256 1=3 4=1 5=1 6=589824 178 | HardSwish Div_544 1 1 719 727 0=1.666667e-01 179 | Convolution Conv_545 1 1 727 728 0=256 1=1 6=65536 180 | Convolution Conv_546 1 1 700_splitncnn_0 729 0=256 1=1 6=131072 181 | Concat Concat_547 2 1 728 729 730 182 | BatchNorm BatchNormalization_548 1 1 730 731 0=512 183 | ReLU LeakyRelu_549 1 1 731 732 0=1.000000e-01 184 | Convolution Conv_550 1 1 732 733 0=512 1=1 5=1 6=262144 185 | HardSwish Div_558 1 1 733 741 0=1.666667e-01 186 | Convolution Conv_559 1 1 639_splitncnn_0 742 0=255 1=1 5=1 6=32640 187 | Reshape Reshape_573 1 1 742 760 0=-1 1=85 2=3 188 | Permute Transpose_574 1 1 760 output 0=1 189 | Convolution Conv_575 1 1 690_splitncnn_0 762 0=255 1=1 5=1 6=65280 190 | Reshape Reshape_589 1 1 762 780 0=-1 1=85 2=3 191 | Permute Transpose_590 1 1 780 781 0=1 192 | Convolution Conv_591 1 1 741 782 0=255 1=1 5=1 6=130560 193 | Reshape Reshape_605 1 1 782 800 0=-1 1=85 2=3 194 | Permute Transpose_606 1 1 800 801 0=1 195 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/yolov5ncnn/MainActivity.java: -------------------------------------------------------------------------------- 1 | // Tencent is pleased to support the open source community by making ncnn available. 2 | // 3 | // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. 4 | // 5 | // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | // in compliance with the License. You may obtain a copy of the License at 7 | // 8 | // https://opensource.org/licenses/BSD-3-Clause 9 | // 10 | // Unless required by applicable law or agreed to in writing, software distributed 11 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | // specific language governing permissions and limitations under the License. 14 | 15 | package com.tencent.yolov5ncnn; 16 | 17 | import android.app.Activity; 18 | import android.content.Intent; 19 | import android.graphics.Bitmap; 20 | import android.graphics.BitmapFactory; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.Paint; 24 | import android.media.ExifInterface; 25 | import android.graphics.Matrix; 26 | import android.net.Uri; 27 | import android.os.Bundle; 28 | import android.util.Log; 29 | import android.view.View; 30 | import android.widget.Button; 31 | import android.widget.ImageView; 32 | 33 | import java.io.FileNotFoundException; 34 | import java.io.InputStream; 35 | import java.io.IOException; 36 | 37 | public class MainActivity extends Activity 38 | { 39 | private static final int SELECT_IMAGE = 1; 40 | 41 | private ImageView imageView; 42 | private Bitmap bitmap = null; 43 | private Bitmap yourSelectedImage = null; 44 | 45 | private YoloV5Ncnn yolov5ncnn = new YoloV5Ncnn(); 46 | 47 | /** Called when the activity is first created. */ 48 | @Override 49 | public void onCreate(Bundle savedInstanceState) 50 | { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.main); 53 | 54 | boolean ret_init = yolov5ncnn.Init(getAssets()); 55 | if (!ret_init) 56 | { 57 | Log.e("MainActivity", "yolov5ncnn Init failed"); 58 | } 59 | 60 | imageView = (ImageView) findViewById(R.id.imageView); 61 | 62 | Button buttonImage = (Button) findViewById(R.id.buttonImage); 63 | buttonImage.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View arg0) { 66 | Intent i = new Intent(Intent.ACTION_PICK); 67 | i.setType("image/*"); 68 | startActivityForResult(i, SELECT_IMAGE); 69 | } 70 | }); 71 | 72 | Button buttonDetect = (Button) findViewById(R.id.buttonDetect); 73 | buttonDetect.setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View arg0) { 76 | if (yourSelectedImage == null) 77 | return; 78 | 79 | YoloV5Ncnn.Obj[] objects = yolov5ncnn.Detect(yourSelectedImage, false); 80 | 81 | showObjects(objects); 82 | } 83 | }); 84 | 85 | Button buttonDetectGPU = (Button) findViewById(R.id.buttonDetectGPU); 86 | buttonDetectGPU.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View arg0) { 89 | if (yourSelectedImage == null) 90 | return; 91 | 92 | YoloV5Ncnn.Obj[] objects = yolov5ncnn.Detect(yourSelectedImage, true); 93 | 94 | showObjects(objects); 95 | } 96 | }); 97 | } 98 | 99 | private void showObjects(YoloV5Ncnn.Obj[] objects) 100 | { 101 | if (objects == null) 102 | { 103 | imageView.setImageBitmap(bitmap); 104 | return; 105 | } 106 | 107 | // draw objects on bitmap 108 | Bitmap rgba = bitmap.copy(Bitmap.Config.ARGB_8888, true); 109 | 110 | final int[] colors = new int[] { 111 | Color.rgb( 54, 67, 244), 112 | Color.rgb( 99, 30, 233), 113 | Color.rgb(176, 39, 156), 114 | Color.rgb(183, 58, 103), 115 | Color.rgb(181, 81, 63), 116 | Color.rgb(243, 150, 33), 117 | Color.rgb(244, 169, 3), 118 | Color.rgb(212, 188, 0), 119 | Color.rgb(136, 150, 0), 120 | Color.rgb( 80, 175, 76), 121 | Color.rgb( 74, 195, 139), 122 | Color.rgb( 57, 220, 205), 123 | Color.rgb( 59, 235, 255), 124 | Color.rgb( 7, 193, 255), 125 | Color.rgb( 0, 152, 255), 126 | Color.rgb( 34, 87, 255), 127 | Color.rgb( 72, 85, 121), 128 | Color.rgb(158, 158, 158), 129 | Color.rgb(139, 125, 96) 130 | }; 131 | 132 | Canvas canvas = new Canvas(rgba); 133 | 134 | Paint paint = new Paint(); 135 | paint.setStyle(Paint.Style.STROKE); 136 | paint.setStrokeWidth(4); 137 | 138 | Paint textbgpaint = new Paint(); 139 | textbgpaint.setColor(Color.WHITE); 140 | textbgpaint.setStyle(Paint.Style.FILL); 141 | 142 | Paint textpaint = new Paint(); 143 | textpaint.setColor(Color.BLACK); 144 | textpaint.setTextSize(26); 145 | textpaint.setTextAlign(Paint.Align.LEFT); 146 | 147 | for (int i = 0; i < objects.length; i++) 148 | { 149 | paint.setColor(colors[i % 19]); 150 | 151 | canvas.drawRect(objects[i].x, objects[i].y, objects[i].x + objects[i].w, objects[i].y + objects[i].h, paint); 152 | 153 | // draw filled text inside image 154 | { 155 | String text = objects[i].label + " = " + String.format("%.1f", objects[i].prob * 100) + "%"; 156 | 157 | float text_width = textpaint.measureText(text); 158 | float text_height = - textpaint.ascent() + textpaint.descent(); 159 | 160 | float x = objects[i].x; 161 | float y = objects[i].y - text_height; 162 | if (y < 0) 163 | y = 0; 164 | if (x + text_width > rgba.getWidth()) 165 | x = rgba.getWidth() - text_width; 166 | 167 | canvas.drawRect(x, y, x + text_width, y + text_height, textbgpaint); 168 | 169 | canvas.drawText(text, x, y - textpaint.ascent(), textpaint); 170 | } 171 | } 172 | 173 | imageView.setImageBitmap(rgba); 174 | } 175 | 176 | @Override 177 | protected void onActivityResult(int requestCode, int resultCode, Intent data) 178 | { 179 | super.onActivityResult(requestCode, resultCode, data); 180 | 181 | if (resultCode == RESULT_OK && null != data) { 182 | Uri selectedImage = data.getData(); 183 | 184 | try 185 | { 186 | if (requestCode == SELECT_IMAGE) { 187 | bitmap = decodeUri(selectedImage); 188 | 189 | yourSelectedImage = bitmap.copy(Bitmap.Config.ARGB_8888, true); 190 | 191 | imageView.setImageBitmap(bitmap); 192 | } 193 | } 194 | catch (FileNotFoundException e) 195 | { 196 | Log.e("MainActivity", "FileNotFoundException"); 197 | return; 198 | } 199 | } 200 | } 201 | 202 | private Bitmap decodeUri(Uri selectedImage) throws FileNotFoundException 203 | { 204 | // Decode image size 205 | BitmapFactory.Options o = new BitmapFactory.Options(); 206 | o.inJustDecodeBounds = true; 207 | BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o); 208 | 209 | // The new size we want to scale to 210 | final int REQUIRED_SIZE = 640; 211 | 212 | // Find the correct scale value. It should be the power of 2. 213 | int width_tmp = o.outWidth, height_tmp = o.outHeight; 214 | int scale = 1; 215 | while (true) { 216 | if (width_tmp / 2 < REQUIRED_SIZE 217 | || height_tmp / 2 < REQUIRED_SIZE) { 218 | break; 219 | } 220 | width_tmp /= 2; 221 | height_tmp /= 2; 222 | scale *= 2; 223 | } 224 | 225 | // Decode with inSampleSize 226 | BitmapFactory.Options o2 = new BitmapFactory.Options(); 227 | o2.inSampleSize = scale; 228 | Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o2); 229 | 230 | // Rotate according to EXIF 231 | int rotate = 0; 232 | try 233 | { 234 | ExifInterface exif = new ExifInterface(getContentResolver().openInputStream(selectedImage)); 235 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 236 | switch (orientation) { 237 | case ExifInterface.ORIENTATION_ROTATE_270: 238 | rotate = 270; 239 | break; 240 | case ExifInterface.ORIENTATION_ROTATE_180: 241 | rotate = 180; 242 | break; 243 | case ExifInterface.ORIENTATION_ROTATE_90: 244 | rotate = 90; 245 | break; 246 | } 247 | } 248 | catch (IOException e) 249 | { 250 | Log.e("MainActivity", "ExifInterface IOException"); 251 | } 252 | 253 | Matrix matrix = new Matrix(); 254 | matrix.postRotate(rotate); 255 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/yolov5ncnn/YoloV5Ncnn.java: -------------------------------------------------------------------------------- 1 | // Tencent is pleased to support the open source community by making ncnn available. 2 | // 3 | // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. 4 | // 5 | // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | // in compliance with the License. You may obtain a copy of the License at 7 | // 8 | // https://opensource.org/licenses/BSD-3-Clause 9 | // 10 | // Unless required by applicable law or agreed to in writing, software distributed 11 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | // specific language governing permissions and limitations under the License. 14 | 15 | package com.tencent.yolov5ncnn; 16 | 17 | import android.content.res.AssetManager; 18 | import android.graphics.Bitmap; 19 | 20 | public class YoloV5Ncnn 21 | { 22 | public native boolean Init(AssetManager mgr); 23 | 24 | public class Obj 25 | { 26 | public float x; 27 | public float y; 28 | public float w; 29 | public float h; 30 | public String label; 31 | public float prob; 32 | } 33 | 34 | public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); 35 | 36 | static { 37 | System.loadLibrary("yolov5ncnn"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/jni/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(yolov5ncnn) 2 | 3 | cmake_minimum_required(VERSION 3.4.1) 4 | 5 | set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20201218-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn) 6 | find_package(ncnn REQUIRED) 7 | 8 | add_library(yolov5ncnn SHARED yolov5ncnn_jni.cpp) 9 | 10 | target_link_libraries(yolov5ncnn 11 | ncnn 12 | 13 | jnigraphics 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/jni/yolov5ncnn_jni.cpp: -------------------------------------------------------------------------------- 1 | // Tencent is pleased to support the open source community by making ncnn available. 2 | // 3 | // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. 4 | // 5 | // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | // in compliance with the License. You may obtain a copy of the License at 7 | // 8 | // https://opensource.org/licenses/BSD-3-Clause 9 | // 10 | // Unless required by applicable law or agreed to in writing, software distributed 11 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | // specific language governing permissions and limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | // ncnn 25 | #include "layer.h" 26 | #include "net.h" 27 | #include "benchmark.h" 28 | 29 | static ncnn::UnlockedPoolAllocator g_blob_pool_allocator; 30 | static ncnn::PoolAllocator g_workspace_pool_allocator; 31 | 32 | static ncnn::Net yolov5; 33 | 34 | class YoloV5Focus : public ncnn::Layer 35 | { 36 | public: 37 | YoloV5Focus() 38 | { 39 | one_blob_only = true; 40 | } 41 | 42 | virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const 43 | { 44 | int w = bottom_blob.w; 45 | int h = bottom_blob.h; 46 | int channels = bottom_blob.c; 47 | 48 | int outw = w / 2; 49 | int outh = h / 2; 50 | int outc = channels * 4; 51 | 52 | top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator); 53 | if (top_blob.empty()) 54 | return -100; 55 | 56 | #pragma omp parallel for num_threads(opt.num_threads) 57 | for (int p = 0; p < outc; p++) 58 | { 59 | const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2); 60 | float* outptr = top_blob.channel(p); 61 | 62 | for (int i = 0; i < outh; i++) 63 | { 64 | for (int j = 0; j < outw; j++) 65 | { 66 | *outptr = *ptr; 67 | 68 | outptr += 1; 69 | ptr += 2; 70 | } 71 | 72 | ptr += w; 73 | } 74 | } 75 | 76 | return 0; 77 | } 78 | }; 79 | 80 | DEFINE_LAYER_CREATOR(YoloV5Focus) 81 | 82 | struct Object 83 | { 84 | float x; 85 | float y; 86 | float w; 87 | float h; 88 | int label; 89 | float prob; 90 | }; 91 | 92 | static inline float intersection_area(const Object& a, const Object& b) 93 | { 94 | if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y) 95 | { 96 | // no intersection 97 | return 0.f; 98 | } 99 | 100 | float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x); 101 | float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y); 102 | 103 | return inter_width * inter_height; 104 | } 105 | 106 | static void qsort_descent_inplace(std::vector& faceobjects, int left, int right) 107 | { 108 | int i = left; 109 | int j = right; 110 | float p = faceobjects[(left + right) / 2].prob; 111 | 112 | while (i <= j) 113 | { 114 | while (faceobjects[i].prob > p) 115 | i++; 116 | 117 | while (faceobjects[j].prob < p) 118 | j--; 119 | 120 | if (i <= j) 121 | { 122 | // swap 123 | std::swap(faceobjects[i], faceobjects[j]); 124 | 125 | i++; 126 | j--; 127 | } 128 | } 129 | 130 | #pragma omp parallel sections 131 | { 132 | #pragma omp section 133 | { 134 | if (left < j) qsort_descent_inplace(faceobjects, left, j); 135 | } 136 | #pragma omp section 137 | { 138 | if (i < right) qsort_descent_inplace(faceobjects, i, right); 139 | } 140 | } 141 | } 142 | 143 | static void qsort_descent_inplace(std::vector& faceobjects) 144 | { 145 | if (faceobjects.empty()) 146 | return; 147 | 148 | qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1); 149 | } 150 | 151 | static void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& picked, float nms_threshold) 152 | { 153 | picked.clear(); 154 | 155 | const int n = faceobjects.size(); 156 | 157 | std::vector areas(n); 158 | for (int i = 0; i < n; i++) 159 | { 160 | areas[i] = faceobjects[i].w * faceobjects[i].h; 161 | } 162 | 163 | for (int i = 0; i < n; i++) 164 | { 165 | const Object& a = faceobjects[i]; 166 | 167 | int keep = 1; 168 | for (int j = 0; j < (int)picked.size(); j++) 169 | { 170 | const Object& b = faceobjects[picked[j]]; 171 | 172 | // intersection over union 173 | float inter_area = intersection_area(a, b); 174 | float union_area = areas[i] + areas[picked[j]] - inter_area; 175 | // float IoU = inter_area / union_area 176 | if (inter_area / union_area > nms_threshold) 177 | keep = 0; 178 | } 179 | 180 | if (keep) 181 | picked.push_back(i); 182 | } 183 | } 184 | 185 | static inline float sigmoid(float x) 186 | { 187 | return static_cast(1.f / (1.f + exp(-x))); 188 | } 189 | 190 | static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector& objects) 191 | { 192 | const int num_grid = feat_blob.h; 193 | 194 | int num_grid_x; 195 | int num_grid_y; 196 | if (in_pad.w > in_pad.h) 197 | { 198 | num_grid_x = in_pad.w / stride; 199 | num_grid_y = num_grid / num_grid_x; 200 | } 201 | else 202 | { 203 | num_grid_y = in_pad.h / stride; 204 | num_grid_x = num_grid / num_grid_y; 205 | } 206 | 207 | const int num_class = feat_blob.w - 5; 208 | 209 | const int num_anchors = anchors.w / 2; 210 | 211 | for (int q = 0; q < num_anchors; q++) 212 | { 213 | const float anchor_w = anchors[q * 2]; 214 | const float anchor_h = anchors[q * 2 + 1]; 215 | 216 | const ncnn::Mat feat = feat_blob.channel(q); 217 | 218 | for (int i = 0; i < num_grid_y; i++) 219 | { 220 | for (int j = 0; j < num_grid_x; j++) 221 | { 222 | const float* featptr = feat.row(i * num_grid_x + j); 223 | 224 | // find class index with max class score 225 | int class_index = 0; 226 | float class_score = -FLT_MAX; 227 | for (int k = 0; k < num_class; k++) 228 | { 229 | float score = featptr[5 + k]; 230 | if (score > class_score) 231 | { 232 | class_index = k; 233 | class_score = score; 234 | } 235 | } 236 | 237 | float box_score = featptr[4]; 238 | 239 | float confidence = sigmoid(box_score) * sigmoid(class_score); 240 | 241 | if (confidence >= prob_threshold) 242 | { 243 | // yolov5/models/yolo.py Detect forward 244 | // y = x[i].sigmoid() 245 | // y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy 246 | // y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh 247 | 248 | float dx = sigmoid(featptr[0]); 249 | float dy = sigmoid(featptr[1]); 250 | float dw = sigmoid(featptr[2]); 251 | float dh = sigmoid(featptr[3]); 252 | 253 | float pb_cx = (dx * 2.f - 0.5f + j) * stride; 254 | float pb_cy = (dy * 2.f - 0.5f + i) * stride; 255 | 256 | float pb_w = pow(dw * 2.f, 2) * anchor_w; 257 | float pb_h = pow(dh * 2.f, 2) * anchor_h; 258 | 259 | float x0 = pb_cx - pb_w * 0.5f; 260 | float y0 = pb_cy - pb_h * 0.5f; 261 | float x1 = pb_cx + pb_w * 0.5f; 262 | float y1 = pb_cy + pb_h * 0.5f; 263 | 264 | Object obj; 265 | obj.x = x0; 266 | obj.y = y0; 267 | obj.w = x1 - x0; 268 | obj.h = y1 - y0; 269 | obj.label = class_index; 270 | obj.prob = confidence; 271 | 272 | objects.push_back(obj); 273 | } 274 | } 275 | } 276 | } 277 | } 278 | 279 | extern "C" { 280 | 281 | // FIXME DeleteGlobalRef is missing for objCls 282 | static jclass objCls = NULL; 283 | static jmethodID constructortorId; 284 | static jfieldID xId; 285 | static jfieldID yId; 286 | static jfieldID wId; 287 | static jfieldID hId; 288 | static jfieldID labelId; 289 | static jfieldID probId; 290 | 291 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) 292 | { 293 | __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "JNI_OnLoad"); 294 | 295 | ncnn::create_gpu_instance(); 296 | 297 | return JNI_VERSION_1_4; 298 | } 299 | 300 | JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) 301 | { 302 | __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "JNI_OnUnload"); 303 | 304 | ncnn::destroy_gpu_instance(); 305 | } 306 | 307 | // public native boolean Init(AssetManager mgr); 308 | JNIEXPORT jboolean JNICALL Java_com_tencent_yolov5ncnn_YoloV5Ncnn_Init(JNIEnv* env, jobject thiz, jobject assetManager) 309 | { 310 | ncnn::Option opt; 311 | opt.lightmode = true; 312 | opt.num_threads = 4; 313 | opt.blob_allocator = &g_blob_pool_allocator; 314 | opt.workspace_allocator = &g_workspace_pool_allocator; 315 | opt.use_packing_layout = true; 316 | 317 | // use vulkan compute 318 | if (ncnn::get_gpu_count() != 0) 319 | opt.use_vulkan_compute = true; 320 | 321 | AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); 322 | 323 | yolov5.opt = opt; 324 | 325 | yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator); 326 | 327 | // init param 328 | { 329 | int ret = yolov5.load_param(mgr, "yolov5s.param"); 330 | if (ret != 0) 331 | { 332 | __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "load_param failed"); 333 | return JNI_FALSE; 334 | } 335 | } 336 | 337 | // init bin 338 | { 339 | int ret = yolov5.load_model(mgr, "yolov5s.bin"); 340 | if (ret != 0) 341 | { 342 | __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "load_model failed"); 343 | return JNI_FALSE; 344 | } 345 | } 346 | 347 | // init jni glue 348 | jclass localObjCls = env->FindClass("com/tencent/yolov5ncnn/YoloV5Ncnn$Obj"); 349 | objCls = reinterpret_cast(env->NewGlobalRef(localObjCls)); 350 | 351 | constructortorId = env->GetMethodID(objCls, "", "(Lcom/tencent/yolov5ncnn/YoloV5Ncnn;)V"); 352 | 353 | xId = env->GetFieldID(objCls, "x", "F"); 354 | yId = env->GetFieldID(objCls, "y", "F"); 355 | wId = env->GetFieldID(objCls, "w", "F"); 356 | hId = env->GetFieldID(objCls, "h", "F"); 357 | labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;"); 358 | probId = env->GetFieldID(objCls, "prob", "F"); 359 | 360 | return JNI_TRUE; 361 | } 362 | 363 | // public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); 364 | JNIEXPORT jobjectArray JNICALL Java_com_tencent_yolov5ncnn_YoloV5Ncnn_Detect(JNIEnv* env, jobject thiz, jobject bitmap, jboolean use_gpu) 365 | { 366 | if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0) 367 | { 368 | return NULL; 369 | //return env->NewStringUTF("no vulkan capable gpu"); 370 | } 371 | 372 | double start_time = ncnn::get_current_time(); 373 | 374 | AndroidBitmapInfo info; 375 | AndroidBitmap_getInfo(env, bitmap, &info); 376 | const int width = info.width; 377 | const int height = info.height; 378 | if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) 379 | return NULL; 380 | 381 | // ncnn from bitmap 382 | const int target_size = 640; 383 | 384 | // letterbox pad to multiple of 32 385 | int w = width; 386 | int h = height; 387 | float scale = 1.f; 388 | if (w > h) 389 | { 390 | scale = (float)target_size / w; 391 | w = target_size; 392 | h = h * scale; 393 | } 394 | else 395 | { 396 | scale = (float)target_size / h; 397 | h = target_size; 398 | w = w * scale; 399 | } 400 | 401 | ncnn::Mat in = ncnn::Mat::from_android_bitmap_resize(env, bitmap, ncnn::Mat::PIXEL_RGB, w, h); 402 | 403 | // pad to target_size rectangle 404 | // yolov5/utils/datasets.py letterbox 405 | int wpad = (w + 31) / 32 * 32 - w; 406 | int hpad = (h + 31) / 32 * 32 - h; 407 | ncnn::Mat in_pad; 408 | ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f); 409 | 410 | // yolov5 411 | std::vector objects; 412 | { 413 | const float prob_threshold = 0.25f; 414 | const float nms_threshold = 0.45f; 415 | 416 | const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f}; 417 | in_pad.substract_mean_normalize(0, norm_vals); 418 | 419 | ncnn::Extractor ex = yolov5.create_extractor(); 420 | 421 | ex.set_vulkan_compute(use_gpu); 422 | 423 | ex.input("images", in_pad); 424 | 425 | std::vector proposals; 426 | 427 | // anchor setting from yolov5/models/yolov5s.yaml 428 | 429 | // stride 8 430 | { 431 | ncnn::Mat out; 432 | ex.extract("output", out); 433 | 434 | ncnn::Mat anchors(6); 435 | anchors[0] = 10.f; 436 | anchors[1] = 13.f; 437 | anchors[2] = 16.f; 438 | anchors[3] = 30.f; 439 | anchors[4] = 33.f; 440 | anchors[5] = 23.f; 441 | 442 | std::vector objects8; 443 | generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8); 444 | 445 | proposals.insert(proposals.end(), objects8.begin(), objects8.end()); 446 | } 447 | 448 | // stride 16 449 | { 450 | ncnn::Mat out; 451 | ex.extract("781", out); 452 | 453 | ncnn::Mat anchors(6); 454 | anchors[0] = 30.f; 455 | anchors[1] = 61.f; 456 | anchors[2] = 62.f; 457 | anchors[3] = 45.f; 458 | anchors[4] = 59.f; 459 | anchors[5] = 119.f; 460 | 461 | std::vector objects16; 462 | generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16); 463 | 464 | proposals.insert(proposals.end(), objects16.begin(), objects16.end()); 465 | } 466 | 467 | // stride 32 468 | { 469 | ncnn::Mat out; 470 | ex.extract("801", out); 471 | 472 | ncnn::Mat anchors(6); 473 | anchors[0] = 116.f; 474 | anchors[1] = 90.f; 475 | anchors[2] = 156.f; 476 | anchors[3] = 198.f; 477 | anchors[4] = 373.f; 478 | anchors[5] = 326.f; 479 | 480 | std::vector objects32; 481 | generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32); 482 | 483 | proposals.insert(proposals.end(), objects32.begin(), objects32.end()); 484 | } 485 | 486 | // sort all proposals by score from highest to lowest 487 | qsort_descent_inplace(proposals); 488 | 489 | // apply nms with nms_threshold 490 | std::vector picked; 491 | nms_sorted_bboxes(proposals, picked, nms_threshold); 492 | 493 | int count = picked.size(); 494 | 495 | objects.resize(count); 496 | for (int i = 0; i < count; i++) 497 | { 498 | objects[i] = proposals[picked[i]]; 499 | 500 | // adjust offset to original unpadded 501 | float x0 = (objects[i].x - (wpad / 2)) / scale; 502 | float y0 = (objects[i].y - (hpad / 2)) / scale; 503 | float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale; 504 | float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale; 505 | 506 | // clip 507 | x0 = std::max(std::min(x0, (float)(width - 1)), 0.f); 508 | y0 = std::max(std::min(y0, (float)(height - 1)), 0.f); 509 | x1 = std::max(std::min(x1, (float)(width - 1)), 0.f); 510 | y1 = std::max(std::min(y1, (float)(height - 1)), 0.f); 511 | 512 | objects[i].x = x0; 513 | objects[i].y = y0; 514 | objects[i].w = x1 - x0; 515 | objects[i].h = y1 - y0; 516 | } 517 | } 518 | 519 | // objects to Obj[] 520 | static const char* class_names[] = { 521 | "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", 522 | "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", 523 | "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", 524 | "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", 525 | "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", 526 | "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", 527 | "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", 528 | "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", 529 | "hair drier", "toothbrush" 530 | }; 531 | 532 | jobjectArray jObjArray = env->NewObjectArray(objects.size(), objCls, NULL); 533 | 534 | for (size_t i=0; iNewObject(objCls, constructortorId, thiz); 537 | 538 | env->SetFloatField(jObj, xId, objects[i].x); 539 | env->SetFloatField(jObj, yId, objects[i].y); 540 | env->SetFloatField(jObj, wId, objects[i].w); 541 | env->SetFloatField(jObj, hId, objects[i].h); 542 | env->SetObjectField(jObj, labelId, env->NewStringUTF(class_names[objects[i].label])); 543 | env->SetFloatField(jObj, probId, objects[i].prob); 544 | 545 | env->SetObjectArrayElement(jObjArray, i, jObj); 546 | } 547 | 548 | double elasped = ncnn::get_current_time() - start_time; 549 | __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "%.2fms detect", elasped); 550 | 551 | return jObjArray; 552 | } 553 | 554 | } 555 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 |