YOLOv8 圖像分類及 .tflite 部署
目標檢測是計算機視覺中用于識別和定位圖像或視頻中對象的一種技術(shù)。圖像定位是使用邊界框來識別一個或多個對象的正確位置的過程,這些邊界框?qū)?yīng)于對象周圍的矩形形狀。這個過程有時會與圖像分類或圖像識別混淆,后者的目標是將圖像或圖像中的對象預(yù)測為一個類別或類別之一。
YOLO開發(fā)者將目標檢測問題構(gòu)建為回歸問題,而不是分類任務(wù),通過空間上分離邊界框,并使用單個卷積神經(jīng)網(wǎng)絡(luò)(CNN)將概率與每個檢測到的圖像相關(guān)聯(lián),如這里所示。
將圖像分割成小單元,并將概率與每個檢測到的圖像關(guān)聯(lián)
YOLO在競爭中領(lǐng)先的原因包括其:
- 速度
- 檢測精度
- 良好的泛化能力
- 開源
將YOLO模型部署到Flutter應(yīng)用程序中,通過手機相機識別對象:
1. 創(chuàng)建新項目并設(shè)置您的環(huán)境:
在android/app/build.gradle中,在android塊中添加以下設(shè)置。
android{
aaptOptions {
noCompress 'tflite'
noCompress 'lite'
}
}
在同一路徑的android/app/build.gradle中,調(diào)整“minSdkVersion”,“targetSdkVersion”和“compileSdkVersion”在android塊如下所示:
android/app/build.gradle --> android塊
在android/build.gradle中,調(diào)整“ext.kotlin_version”構(gòu)建腳本塊為此:
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
}
2. 將您的Yolov8模型添加到項目環(huán)境中:
模型必須以“.tflite”的形式導(dǎo)出,以便于在邊緣設(shè)備上部署,如手機。如果您想用Python訓(xùn)練自定義的Yolov模型并將其導(dǎo)出為.tflite而不是.pt,您必須遵循這里的說明。我將在這里插入一個用于測試的預(yù)訓(xùn)練好的Yolov8目標檢測模型。在您的Flutter項目中:
(1) 創(chuàng)建一個assets文件夾,并將標簽文件和模型文件放在其中。在pubspec.yaml中添加:
assets:
- assets/labels.txt
- assets/yolov8n.tflite
(2) 導(dǎo)入所需的包flutter_vision / camera:
import 'package:flutter_vision/flutter_vision.dart';
import 'package:camera/camera.dart';
3. 開始編程:
(1) 在您的項目中初始化相機如下:
late List<CameraDescription> camerass;
(2) 創(chuàng)建“YoloVideo”類:
class YoloVideo extends StatefulWidget {
const YoloVideo({Key? key}) : super(key: key);
@override
State<YoloVideo> createState() => _YoloVideoState();
}
class _YoloVideoState extends State<YoloVideo> {
}
(3) 在“YoloVideo”類中,聲明所需的變量:
late CameraController controller;
late FlutterVision vision;
late List<Map<String, dynamic>> yoloResults;
CameraImage? cameraImage;
bool isLoaded = false;
bool isDetecting = false;
double confidenceThreshold = 0.5;
(4) 初始化模型和相機:
@override
void initState() {
super.initState();
init();
}
init() async {
camerass = await availableCameras();
vision = FlutterVision();
controller = CameraController(camerass[0], ResolutionPreset.high);
controller.initialize().then((value) {
loadYoloModel().then((value) {
setState(() {
isLoaded = true;
isDetecting = false;
yoloResults = [];
});
});
});
}
@override
void dispose() async {
super.dispose();
controller.dispose();
await vision.closeYoloModel();
}
(5) 簡單的UI和確定視頻流大小
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
if (!isLoaded) {
return const Scaffold(
body: Center(
child: Text("Model not loaded, waiting for it"),
),
);
}
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(
controller,
),
),
...displayBoxesAroundRecognizedObjects(size),
Positioned(
bottom: 75,
width: MediaQuery.of(context).size.width,
child: Container(
height: 80,
width: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 5, color: Colors.white, style: BorderStyle.solid),
),
child: isDetecting
? IconButton(
onPressed: () async {
stopDetection();
},
icon: const Icon(
Icons.stop,
color: Colors.red,
),
iconSize: 50,
)
: IconButton(
onPressed: () async {
await startDetection();
},
icon: const Icon(
Icons.play_arrow,
color: Colors.white,
),
iconSize: 50,
),
),
),
],
),
);
}
(6) 加載模型
Future<void> loadYoloModel() async {
await vision.loadYoloModel(
labels: 'assets/CLASSES.txt',
modelPath: 'assets/curr_float32.tflite',
modelVersion: "yolov8",
numThreads: 1,
useGpu: true);
setState(() {
isLoaded = true;
});
}
// 通過yoloOnFrame進行實時目標檢測函數(shù)
Future<void> yoloOnFrame(CameraImage cameraImage) async {
final result = await vision.yoloOnFrame(
bytesList: cameraImage.planes.map((plane) => plane.bytes).toList(),
imageHeight: cameraImage.height,
imageWidth: cameraImage.width,
iouThreshold: 0.4,
confThreshold: 0.4,
classThreshold: 0.5);
if (result.isNotEmpty) {
setState(() {
yoloResults = result;
});
}
}
(7) 啟動視頻流和開始或停止檢測的函數(shù)
Future<void> startDetection() async {
setState(() {
isDetecting = true;
});
if (controller.value.isStreamingImages) {
return;
}
await controller.startImageStream((image) async {
if (isDetecting) {
cameraImage = image;
yoloOnFrame(image);
}
});
}
Future<void> stopDetection() async {
setState(() {
isDetecting = false;
yoloResults.clear();
});
}
(8) 檢測到的對象周圍的邊界框
List<Widget> displayBoxesAroundRecognizedObjects(Size screen) {
if (yoloResults.isEmpty) return [];
double factorX = screen.width / (cameraImage?.height ?? 1);
double factorY = screen.height / (cameraImage?.width ?? 1);
Color colorPick = const Color.fromARGB(255, 50, 233, 30);
return yoloResults.map((result) {
double objectX = result["box"][0] * factorX;
double objectY = result["box"][1] * factorY;
double objectWidth = (result["box"][2] - result["box"][0]) * factorX;
double objectHeight = (result["box"][3] - result["box"][1]) * factorY;
speak() {
String currentResult = result['tag'].toString();
DateTime currentTime = DateTime.now();
if (currentResult != previousResult ||
currentTime.difference(previousSpeechTime) >= repeatDuration) {
tts.flutterSpeak(currentResult);
previousResult = currentResult;
previousSpeechTime = currentTime;
}
}
speak();
return Positioned(
left: objectX,
top: objectY,
width: objectWidth,
height: objectHeight,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
border: Border.all(color: Colors.pink, width: 2.0),
),
child: Text(
"${result['tag']} ${((result['box'][4] * 100).toStringAsFixed(0))}",
style: TextStyle(
background: Paint()..color = colorPick,
color: const Color.fromARGB(255, 115, 0, 255),
fontSize: 18.0,
),
),
),
);
}).toList();
}
}
最后,您可以在Main.dart中將YoloVideo類作為函數(shù)調(diào)用,以在啟動應(yīng)用程序時啟動視頻流和實時目標檢測,如下所示:
main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
const MaterialApp(
home: YoloVideo(),
),
);
}
我的Yolov8 Flutter應(yīng)用程序的截圖