Визуальная/изображительная информация, доставленная в кратчайшие сроки!
Предисловие
Yolov8 — популярный ИИ для обнаружения объектов. Android — самая популярная мобильная операционная система в мире.
В этой статье описывается, как выполнить обнаружение объектов yolov8 на устройствах Android.
Шаг 1. Конвертируйте формат Pytorch в формат tflite.
YOLOv8 построен в формате pytorch. Конвертируйте его в tflite для использования на Android.
Установить YOLOv8
Установите фреймворк под названием Ultralytics. Yolov8 включен в эту структуру.
pip install ultralytics
Конвертировать в тфлайт
Преобразование с использованием кодов преобразования. Следующий код загрузит веса предварительно обученной модели.
Если у вас есть файл контрольных точек веса для модели, обученной с использованием ваших собственных данных, замените раздел yolov8s.pt.
from ultralytics import YOLO
model = YOLO('yolov8s.pt')
model.export(format="tflite")
yolov8s_saved_model/yolov8s_float16.tflite будет создан, поэтому используйте его.
Если произошла ошибка преобразования...
Если возникает следующая ошибка, это связано с версией tensorflow, поэтому установите совместимую версию.
ImportError: generic_type: невозможно инициализировать тип «StatusCode»: объект с таким именем уже определен
Например, измените tensorflow на следующую версию.
pip install tensorflow==2.13.0
Запуск файлов tflite на Android
Отсюда мы запустим файл yolov8 tflite в проекте студии Android.
Добавьте файл tflite в проект
Создайте каталог ресурсов (Файл → Создать → Папка → Папка активов) в каталоге приложения проекта студии Android и добавьте файл tflite (yolov8s_float32.tflite) и labels.txt, которые можно добавить путем копирования и вставки.
labels.txt — это текстовый файл, описывающий имя класса модели YOLOv8, как показано ниже.
Если вы установили собственный класс, напишите этот класс.
Предварительно обученная модель YOLOv8 по умолчанию выглядит следующим образом.
Содержимое файла labels.txt следующее:
person
bicycle
car
motorcycle
airplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
couch
potted plant
bed
dining table
toilet
tv
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush
Установить тфлайт
Добавьте следующее в app/build.gradle.kts Зависимости в к Установить тфлайт рамка.
приложение/build.gradle.kts
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
После добавления вышеуказанного контента нажмите «Синхронизировать сейчас», чтобы установить.
Импортируйте необходимые модули
import org.tensorflow.lite.DataType
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.gpu.CompatibilityList
import org.tensorflow.lite.gpu.GpuDelegate
import org.tensorflow.lite.support.common.FileUtil
import org.tensorflow.lite.support.common.ops.CastOp
import org.tensorflow.lite.support.common.ops.NormalizeOp
import org.tensorflow.lite.support.image.ImageProcessor
import org.tensorflow.lite.support.image.TensorImage
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
необходимые атрибуты класса
private val modelPath = "yolov8s_float32.tflite"
private val labelPath = "labels.txt"
private var interpreter: Interpreter? = null
private var tensorWidth = 0
private var tensorHeight = 0
private var numChannel = 0
private var numElements = 0
private var labels = mutableListOf<String>()
private val imageProcessor = ImageProcessor.Builder()
.add(NormalizeOp(INPUT_MEAN, INPUT_STANDARD_DEVIATION))
.add(CastOp(INPUT_IMAGE_TYPE))
.build() // preprocess input
companion object {
private const val INPUT_MEAN = 0f
private const val INPUT_STANDARD_DEVIATION = 255f
private val INPUT_IMAGE_TYPE = DataType.FLOAT32
private val OUTPUT_IMAGE_TYPE = DataType.FLOAT32
private const val CONFIDENCE_THRESHOLD = 0.3F
private const val IOU_THRESHOLD = 0.5F
}
Инициализировать модель
Инициализируйте модель tflite. Получите файл модели и передайте его интерпретатору tflite. При необходимости передайте количество используемых потоков.
Если вы используете его в классе, отличном от Activity, вам необходимо передать контекст этому классу.
val model = FileUtil.loadMappedFile(context, modelPath)
val options = Interpreter.Options()
options.numThreads = 4
interpreter = Interpreter(model, options)
Получите входные и выходные формы yolov8 из интерпретатора.
val inputShape = interpreter.getInputTensor(0).shape()
val outputShape = interpreter.getOutputTensor(0).shape()
tensorWidth = inputShape[1]
tensorHeight = inputShape[2]
numChannel = outputShape[1]
numElements = outputShape[2]
Прочтите имя класса из файла label.txt.
InputStream и InputStreamReader должны быть закрыты явно.
try {
val inputStream: InputStream = context.assets.open(labelPath)
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String? = reader.readLine()
while (line != null && line != "") {
labels.add(line)
line = reader.readLine()
}
reader.close()
inputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
Введите изображение и выполните
Входные данные представляют собой растровое изображение, но следующая предварительная обработка выполняется в соответствии с входным форматом модели.
1. Измените размер, чтобы он соответствовал входной форме модели.
2. Сделайте это тензором
3. Нормализуйте значение пикселя, разделив его на 255 (придав ему значение в диапазоне от 0 до 1).
4. Преобразование во входной тип модели.
5. Введите, чтобы получить imageBuffer
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, tensorWidth, tensorHeight, false)
val tensorImage = TensorImage(DataType.FLOAT32)
tensorImage.load(resizedBitmap)
val processedImage = imageProcessor.process(tensorImage)
val imageBuffer = processedImage.buffer
Создайте выходной тензорный буфер, соответствующий выходной форме модели, и передайте его интерпретатору для выполнения вместе с входным imageBuffer, указанным выше.
val output = TensorBuffer.createFixedSize(intArrayOf(1 , numChannel, numElements), OUTPUT_IMAGE_TYPE)
interpreter.run(imageBuffer, output.buffer)
Постобработка вывода
Поле вывода рассматривается как класс BoudingBox.
Это класс с классом, коробкой и уровнем доверия.
x1, y1 — начальная точка. x2, y2 — конечные точки. cx, cy — центры. w — ширина, h — высота.
data class BoundingBox(
val x1: Float,
val y1: Float,
val x2: Float,
val y2: Float,
val cx: Float,
val cy: Float,
val w: Float,
val h: Float,
val cnf: Float,
val cls: Int,
val clsName: String
)
Следующий процесс — выбрать более надежный блок из множества кандидатов на выходной блок.
1. Извлеките поля, достоверность которых превышает порог достоверности.
2. Среди перекрывающихся коробок сохранить коробку с наибольшей надежностью. (нмс)
private fun bestBox(array: FloatArray) : List<BoundingBox>? {
val boundingBoxes = mutableListOf<BoundingBox>()
for (c in 0 until numElements) {
var maxConf = -1.0f
var maxIdx = -1
var j = 4
var arrayIdx = c + numElements * j
while (j < numChannel){
if (array[arrayIdx] > maxConf) {
maxConf = array[arrayIdx]
maxIdx = j - 4
}
j++
arrayIdx += numElements
}
if (maxConf > CONFIDENCE_THRESHOLD) {
val clsName = labels[maxIdx]
val cx = array[c] // 0
val cy = array[c + numElements] // 1
val w = array[c + numElements * 2]
val h = array[c + numElements * 3]
val x1 = cx - (w/2F)
val y1 = cy - (h/2F)
val x2 = cx + (w/2F)
val y2 = cy + (h/2F)
if (x1 < 0F || x1 > 1F) continue
if (y1 < 0F || y1 > 1F) continue
if (x2 < 0F || x2 > 1F) continue
if (y2 < 0F || y2 > 1F) continue
boundingBoxes.add(
BoundingBox(
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
cx = cx, cy = cy, w = w, h = h,
cnf = maxConf, cls = maxIdx, clsName = clsName
)
)
}
}
if (boundingBoxes.isEmpty()) return null
return applyNMS(boundingBoxes)
}
private fun applyNMS(boxes: List<BoundingBox>) : MutableList<BoundingBox> {
val sortedBoxes = boxes.sortedByDescending { it.cnf }.toMutableList()
val selectedBoxes = mutableListOf<BoundingBox>()
while(sortedBoxes.isNotEmpty()) {
val first = sortedBoxes.first()
selectedBoxes.add(first)
sortedBoxes.remove(first)
val iterator = sortedBoxes.iterator()
while (iterator.hasNext()) {
val nextBox = iterator.next()
val iou = calculateIoU(first, nextBox)
if (iou >= IOU_THRESHOLD) {
iterator.remove()
}
}
}
return selectedBoxes
}
private fun calculateIoU(box1: BoundingBox, box2: BoundingBox): Float {
val x1 = maxOf(box1.x1, box2.x1)
val y1 = maxOf(box1.y1, box2.y1)
val x2 = minOf(box1.x2, box2.x2)
val y2 = minOf(box1.y2, box2.y2)
val intersectionArea = maxOf(0F, x2 - x1) * maxOf(0F, y2 - y1)
val box1Area = box1.w * box1.h
val box2Area = box2.w * box2.h
return intersectionArea / (box1Area + box2Area - intersectionArea)
}
На этом этапе вы получите выходные данные yolov8.
val bestBoxes = bestBox(output.floatArray)
Нарисуйте поле вывода на изображении
fun drawBoundingBoxes(bitmap: Bitmap, boxes: List<BoundingBox>): Bitmap {
val mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(mutableBitmap)
val paint = Paint().apply {
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = 8f
}
val textPaint = Paint().apply {
color = Color.WHITE
textSize = 40f
typeface = Typeface.DEFAULT_BOLD
}
for (box in boxes) {
val rect = RectF(
box.x1 * mutableBitmap.width,
box.y1 * mutableBitmap.height,
box.x2 * mutableBitmap.width,
box.y2 * mutableBitmap.height
)
canvas.drawRect(rect, paint)
canvas.drawText(box.clsName, rect.left, rect.bottom, textPaint)
}
return mutableBitmap
}
В некоторых случаях путь к модели требуется, когда интерпретатор пуст.