react-native-yolo 0.0.5 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Yolo.podspec +3 -0
- package/android/src/main/java/com/yolo/HybridYolo.kt +12 -235
- package/android/src/main/java/com/yolo/HybridYoloModel.kt +259 -0
- package/android/src/main/java/com/yolo/utils/BitmapOrientationFixer.kt +8 -1
- package/ios/HybridYolo.swift +52 -10
- package/ios/HybridYoloModel.swift +250 -0
- package/ios/Loader/YoloModelLoader.swift +139 -0
- package/ios/Utils/BitmapOrientationFixer.swift +64 -0
- package/ios/Utils/ContextProvider.swift +45 -0
- package/ios/Utils/FrameJpegConverter.swift +41 -0
- package/ios/Utils/FrameValidator.swift +40 -0
- package/ios/Utils/Nv12JpegEncoder.swift +58 -0
- package/ios/Utils/Yuv420ToNv12Converter.swift +71 -0
- package/lib/commonjs/index.js +4 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +4 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +12 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/yolo.nitro.d.ts +10 -6
- package/lib/typescript/src/specs/yolo.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/Yolo+autolinking.cmake +2 -0
- package/nitrogen/generated/android/YoloOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JHybridYoloModelSpec.cpp +78 -0
- package/nitrogen/generated/android/c++/JHybridYoloModelSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JHybridYoloSpec.cpp +9 -32
- package/nitrogen/generated/android/c++/JHybridYoloSpec.hpp +1 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/yolo/HybridYoloModelSpec.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/yolo/HybridYoloSpec.kt +1 -9
- package/nitrogen/generated/ios/Yolo-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/Yolo-Swift-Cxx-Bridge.hpp +48 -31
- package/nitrogen/generated/ios/Yolo-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/c++/HybridYoloModelSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridYoloModelSpecSwift.hpp +97 -0
- package/nitrogen/generated/ios/c++/HybridYoloSpecSwift.hpp +6 -24
- package/nitrogen/generated/ios/swift/HybridYoloModelSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridYoloModelSpec_cxx.swift +160 -0
- package/nitrogen/generated/ios/swift/HybridYoloSpec.swift +1 -3
- package/nitrogen/generated/ios/swift/HybridYoloSpec_cxx.swift +8 -38
- package/nitrogen/generated/shared/c++/HybridYoloModelSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridYoloModelSpec.hpp +69 -0
- package/nitrogen/generated/shared/c++/HybridYoloSpec.cpp +0 -2
- package/nitrogen/generated/shared/c++/HybridYoloSpec.hpp +5 -8
- package/package.json +1 -1
- package/src/index.ts +14 -4
- package/src/specs/yolo.nitro.ts +12 -3
package/Yolo.podspec
CHANGED
|
@@ -1,265 +1,42 @@
|
|
|
1
1
|
package com.yolo
|
|
2
2
|
|
|
3
|
-
import android.
|
|
3
|
+
import android.util.Base64
|
|
4
4
|
import android.util.Log
|
|
5
|
-
|
|
6
|
-
import com.margelo.nitro.yolo.HybridYoloSpec
|
|
7
|
-
import java.io.File
|
|
8
|
-
import java.io.RandomAccessFile
|
|
9
|
-
import java.net.URL
|
|
10
|
-
import java.nio.MappedByteBuffer
|
|
11
|
-
import java.nio.channels.FileChannel
|
|
12
|
-
import org.tensorflow.lite.Interpreter
|
|
13
|
-
import org.tensorflow.lite.support.common.FileUtil
|
|
14
|
-
import yolo.com.loader.YoloModelLoader
|
|
5
|
+
|
|
15
6
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
7
|
+
import com.margelo.nitro.yolo.HybridYoloModelSpec
|
|
8
|
+
import com.margelo.nitro.yolo.HybridYoloSpec
|
|
16
9
|
|
|
17
|
-
import android.util.Base64
|
|
18
10
|
import com.yolo.utils.FrameJpegConverter
|
|
19
11
|
import com.yolo.utils.FrameValidator
|
|
20
12
|
|
|
21
|
-
import com.margelo.nitro.yolo.Detection
|
|
22
|
-
import com.margelo.nitro.yolo.BoundingBox
|
|
23
|
-
|
|
24
|
-
import java.nio.ByteBuffer
|
|
25
|
-
import java.nio.ByteOrder
|
|
26
|
-
import org.tensorflow.lite.DataType
|
|
27
|
-
import kotlin.math.roundToInt
|
|
28
|
-
|
|
29
13
|
|
|
30
14
|
class HybridYolo : HybridYoloSpec() {
|
|
31
15
|
companion object {
|
|
32
16
|
private const val TAG = "YOLO_TAG"
|
|
33
17
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
private var inputWidth = 0
|
|
38
|
-
private var inputHeight = 0
|
|
39
|
-
private var inputDataType: DataType? = null
|
|
40
|
-
|
|
41
|
-
private val modelLoader = YoloModelLoader()
|
|
42
|
-
|
|
43
|
-
override fun sum(num1: Double, num2: Double): Double {
|
|
44
|
-
return num1 + num2
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
override fun loadModel(modelPath: String) {
|
|
48
|
-
val context =
|
|
49
|
-
NitroModules.applicationContext ?: throw IllegalStateException("Context is null")
|
|
50
|
-
|
|
51
|
-
Log.d(TAG, "Trying to load: $modelPath")
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
val modelBuffer = modelLoader.load(modelPath)
|
|
55
|
-
|
|
56
|
-
interpreter?.close()
|
|
57
|
-
interpreter = Interpreter(modelBuffer)
|
|
58
|
-
|
|
59
|
-
val inputTensor = interpreter!!.getInputTensor(0)
|
|
60
|
-
val shape = inputTensor.shape()
|
|
61
|
-
|
|
62
|
-
inputHeight = shape[1]
|
|
63
|
-
inputWidth = shape[2]
|
|
64
|
-
inputDataType = inputTensor.dataType()
|
|
65
|
-
inputBuffer = modelLoader.makeInputBuffer(interpreter!!)
|
|
66
|
-
|
|
67
|
-
val outputTensor = interpreter!!.getOutputTensor(0)
|
|
68
|
-
Log.d(TAG, "✅ YOLO model loaded")
|
|
69
|
-
Log.d(TAG, "📥 Input shape: ${inputTensor.shape().contentToString()}")
|
|
70
|
-
Log.d(TAG, "📤 Output shape: ${outputTensor.shape().contentToString()}")
|
|
71
|
-
Log.d(TAG, "📥 Input type: ${inputTensor.dataType()}")
|
|
72
|
-
Log.d(TAG, "📤 Output type: ${outputTensor.dataType()}")
|
|
73
|
-
|
|
74
|
-
Log.d(TAG, "Input shape=${shape.contentToString()} type=$inputDataType")
|
|
75
|
-
Log.d(TAG, "✅ Model loaded successfully!")
|
|
76
|
-
} catch (e: Exception) {
|
|
77
|
-
Log.e(TAG, "❌ Failed to load model: ${e.message}", e)
|
|
78
|
-
}
|
|
18
|
+
override fun loadModel(modelPath: String): HybridYoloModelSpec {
|
|
19
|
+
Log.d(TAG, "Trying to load model object: $modelPath")
|
|
20
|
+
return HybridYoloModel(modelPath)
|
|
79
21
|
}
|
|
80
22
|
override fun frameToBase64(frame: HybridFrameSpec): String {
|
|
23
|
+
Log.d(TAG, "Trying to convert frame to base64")
|
|
81
24
|
return try {
|
|
25
|
+
Log.d(TAG, "frameToBase64: frame: $frame")
|
|
82
26
|
if (!FrameValidator.isValidYuv(frame)) return ""
|
|
27
|
+
Log.d(TAG, "frameToBase64: frame is valid YUV")
|
|
83
28
|
|
|
84
29
|
val jpegBytes = FrameJpegConverter.toJpegBytes(
|
|
85
30
|
frame = frame,
|
|
86
31
|
quality = 80
|
|
87
32
|
)
|
|
33
|
+
Log.d(TAG, "frameToBase64: jpegBytes size: ${jpegBytes.size}")
|
|
88
34
|
|
|
89
35
|
Base64.encodeToString(jpegBytes, Base64.NO_WRAP)
|
|
36
|
+
|
|
90
37
|
} catch (e: Exception) {
|
|
91
38
|
Log.e(TAG, "❌ frameToBase64 failed", e)
|
|
92
39
|
""
|
|
93
40
|
}
|
|
94
41
|
}
|
|
95
|
-
|
|
96
|
-
override fun detect(frame: HybridFrameSpec): Array<Detection> {
|
|
97
|
-
val localInterpreter = interpreter ?: run {
|
|
98
|
-
Log.e(TAG, "❌ Model is not loaded. Please call loadModel() first.")
|
|
99
|
-
return emptyArray()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (!FrameValidator.isValidYuv(frame)) {
|
|
103
|
-
Log.e(TAG, "❌ Invalid frame provided for detection.")
|
|
104
|
-
return emptyArray()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
val input = inputBuffer ?: return emptyArray()
|
|
108
|
-
|
|
109
|
-
fillInputFromYuvFrame(
|
|
110
|
-
frame = frame,
|
|
111
|
-
input = input,
|
|
112
|
-
dstWidth = inputWidth,
|
|
113
|
-
dstHeight = inputHeight,
|
|
114
|
-
dataType = inputDataType ?: DataType.FLOAT32
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
val output = Array(1) { Array(300) { FloatArray(6) } }
|
|
118
|
-
|
|
119
|
-
localInterpreter.run(input, output)
|
|
120
|
-
|
|
121
|
-
val detections = parseNmsOutput(output, confidenceThreshold = 0.5f)
|
|
122
|
-
if (detections.isEmpty()) {
|
|
123
|
-
val best = output[0].maxByOrNull { it[4] }
|
|
124
|
-
Log.d(TAG, "No detections. Best row=${best?.contentToString()}")
|
|
125
|
-
} else {
|
|
126
|
-
Log.d(TAG, "✅ Detections: ${detections.size}")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return detections.toTypedArray()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private fun parseNmsOutput(
|
|
134
|
-
output: Array<Array<FloatArray>>,
|
|
135
|
-
confidenceThreshold: Float = 0.5f
|
|
136
|
-
): List<Detection> {
|
|
137
|
-
val detections = mutableListOf<Detection>()
|
|
138
|
-
|
|
139
|
-
val batchMatrix = output[0]
|
|
140
|
-
|
|
141
|
-
for (i in batchMatrix.indices) {
|
|
142
|
-
val row = batchMatrix[i]
|
|
143
|
-
|
|
144
|
-
val score = row[4]
|
|
145
|
-
if (score < confidenceThreshold) continue
|
|
146
|
-
|
|
147
|
-
val x1 = row[0].toDouble()
|
|
148
|
-
val y1 = row[1].toDouble()
|
|
149
|
-
val x2 = row[2].toDouble()
|
|
150
|
-
val y2 = row[3].toDouble()
|
|
151
|
-
val classId = row[5].toDouble()
|
|
152
|
-
|
|
153
|
-
detections.add(
|
|
154
|
-
Detection(
|
|
155
|
-
boundingBox = BoundingBox(
|
|
156
|
-
x1 = x1,
|
|
157
|
-
y1 = y1,
|
|
158
|
-
x2 = x2,
|
|
159
|
-
y2 = y2
|
|
160
|
-
),
|
|
161
|
-
score = score.toDouble(),
|
|
162
|
-
classId = classId
|
|
163
|
-
)
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return detections
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
private fun fillInputFromYuvFrame(
|
|
173
|
-
frame: HybridFrameSpec,
|
|
174
|
-
input: ByteBuffer,
|
|
175
|
-
dstWidth: Int,
|
|
176
|
-
dstHeight: Int,
|
|
177
|
-
dataType: DataType
|
|
178
|
-
) {
|
|
179
|
-
val srcWidth = frame.width.toInt()
|
|
180
|
-
val srcHeight = frame.height.toInt()
|
|
181
|
-
|
|
182
|
-
val planes = frame.getPlanes()
|
|
183
|
-
|
|
184
|
-
val yPlane = planes[0]
|
|
185
|
-
val uPlane = planes[1]
|
|
186
|
-
val vPlane = planes[2]
|
|
187
|
-
|
|
188
|
-
val yBytes = yPlane.getPixelBuffer().toByteArray()
|
|
189
|
-
val uBytes = uPlane.getPixelBuffer().toByteArray()
|
|
190
|
-
val vBytes = vPlane.getPixelBuffer().toByteArray()
|
|
191
|
-
|
|
192
|
-
val yRowStride = yPlane.bytesPerRow.roundToInt()
|
|
193
|
-
val uRowStride = uPlane.bytesPerRow.roundToInt()
|
|
194
|
-
val vRowStride = vPlane.bytesPerRow.roundToInt()
|
|
195
|
-
|
|
196
|
-
input.rewind()
|
|
197
|
-
|
|
198
|
-
for (dy in 0 until dstHeight) {
|
|
199
|
-
// val sy = dy * srcHeight / dstHeight
|
|
200
|
-
|
|
201
|
-
for (dx in 0 until dstWidth) {
|
|
202
|
-
val srcX = dy * srcWidth / dstHeight
|
|
203
|
-
val srcY = srcHeight - 1 - (dx * srcHeight / dstWidth)
|
|
204
|
-
|
|
205
|
-
val yIndex = srcY * yRowStride + srcX
|
|
206
|
-
|
|
207
|
-
val uvX = srcX / 2
|
|
208
|
-
val uvY = srcY / 2
|
|
209
|
-
|
|
210
|
-
val uIndex = uvY * uRowStride + uvX * 2
|
|
211
|
-
val vIndex = uvY * vRowStride + uvX * 2
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
yIndex >= yBytes.size ||
|
|
215
|
-
uIndex >= uBytes.size ||
|
|
216
|
-
vIndex >= vBytes.size
|
|
217
|
-
) {
|
|
218
|
-
continue
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
val y = yBytes[yIndex].toInt() and 0xFF
|
|
222
|
-
val u = uBytes[uIndex].toInt() and 0xFF
|
|
223
|
-
val v = vBytes[vIndex].toInt() and 0xFF
|
|
224
|
-
|
|
225
|
-
val rFloat = y + 1.402f * (v - 128)
|
|
226
|
-
val gFloat = y - 0.344136f * (u - 128) - 0.714136f * (v - 128)
|
|
227
|
-
val bFloat = y + 1.772f * (u - 128)
|
|
228
|
-
|
|
229
|
-
val r = rFloat.roundToInt().coerceIn(0, 255)
|
|
230
|
-
val g = gFloat.roundToInt().coerceIn(0, 255)
|
|
231
|
-
val b = bFloat.roundToInt().coerceIn(0, 255)
|
|
232
|
-
|
|
233
|
-
when (dataType) {
|
|
234
|
-
DataType.FLOAT32 -> {
|
|
235
|
-
input.putFloat(r / 255f)
|
|
236
|
-
input.putFloat(g / 255f)
|
|
237
|
-
input.putFloat(b / 255f)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
DataType.UINT8 -> {
|
|
241
|
-
input.put(r.toByte())
|
|
242
|
-
input.put(g.toByte())
|
|
243
|
-
input.put(b.toByte())
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
else -> error("Unsupported input type: $dataType")
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
input.rewind()
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private fun yuvToRgb(y: Int, u: Int, v: Int): IntArray {
|
|
254
|
-
val yf = y.toFloat()
|
|
255
|
-
val uf = u.toFloat() - 128f
|
|
256
|
-
val vf = v.toFloat() - 128f
|
|
257
|
-
|
|
258
|
-
val r = (yf + 1.402f * vf).roundToInt().coerceIn(0, 255)
|
|
259
|
-
val g = (yf - 0.344136f * uf - 0.714136f * vf).roundToInt().coerceIn(0, 255)
|
|
260
|
-
val b = (yf + 1.772f * uf).roundToInt().coerceIn(0, 255)
|
|
261
|
-
|
|
262
|
-
return intArrayOf(r, g, b)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
42
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
package com.yolo
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.margelo.nitro.yolo.HybridYoloModelSpec
|
|
5
|
+
import com.margelo.nitro.yolo.Detection
|
|
6
|
+
import com.margelo.nitro.yolo.BoundingBox
|
|
7
|
+
import com.margelo.nitro.camera.HybridFrameSpec
|
|
8
|
+
import com.yolo.utils.FrameValidator
|
|
9
|
+
import java.nio.ByteBuffer
|
|
10
|
+
import org.tensorflow.lite.DataType
|
|
11
|
+
import org.tensorflow.lite.Interpreter
|
|
12
|
+
import yolo.com.loader.YoloModelLoader
|
|
13
|
+
import kotlin.math.roundToInt
|
|
14
|
+
import com.margelo.nitro.camera.CameraOrientation
|
|
15
|
+
|
|
16
|
+
class HybridYoloModel(
|
|
17
|
+
modelPath: String
|
|
18
|
+
) : HybridYoloModelSpec() {
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
private const val TAG = "YOLO_MODEL_TAG"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private val modelLoader = YoloModelLoader()
|
|
25
|
+
|
|
26
|
+
private var interpreter: Interpreter? = null
|
|
27
|
+
private var inputBuffer: ByteBuffer? = null
|
|
28
|
+
private var inputWidth = 0
|
|
29
|
+
private var inputHeight = 0
|
|
30
|
+
private var inputDataType: DataType? = null
|
|
31
|
+
|
|
32
|
+
private val lock = Any()
|
|
33
|
+
|
|
34
|
+
init {
|
|
35
|
+
load(modelPath)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private fun load(modelPath: String) {
|
|
39
|
+
try {
|
|
40
|
+
val modelBuffer = modelLoader.load(modelPath)
|
|
41
|
+
|
|
42
|
+
interpreter = Interpreter(modelBuffer)
|
|
43
|
+
|
|
44
|
+
val localInterpreter = interpreter!!
|
|
45
|
+
|
|
46
|
+
val inputTensor = localInterpreter.getInputTensor(0)
|
|
47
|
+
val outputTensor = localInterpreter.getOutputTensor(0)
|
|
48
|
+
|
|
49
|
+
val shape = inputTensor.shape()
|
|
50
|
+
|
|
51
|
+
inputHeight = shape[1]
|
|
52
|
+
inputWidth = shape[2]
|
|
53
|
+
inputDataType = inputTensor.dataType()
|
|
54
|
+
inputBuffer = modelLoader.makeInputBuffer(localInterpreter)
|
|
55
|
+
|
|
56
|
+
Log.d(TAG, "✅ YOLO model instance loaded")
|
|
57
|
+
Log.d(TAG, "📥 Input shape: ${inputTensor.shape().contentToString()}")
|
|
58
|
+
Log.d(TAG, "📤 Output shape: ${outputTensor.shape().contentToString()}")
|
|
59
|
+
Log.d(TAG, "📥 Input type: ${inputTensor.dataType()}")
|
|
60
|
+
Log.d(TAG, "📤 Output type: ${outputTensor.dataType()}")
|
|
61
|
+
} catch (e: Exception) {
|
|
62
|
+
Log.e(TAG, "❌ Failed to load model instance", e)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun detect(frame: HybridFrameSpec): Array<Detection> {
|
|
67
|
+
val localInterpreter = interpreter ?: run {
|
|
68
|
+
Log.e(TAG, "❌ This model instance is not loaded.")
|
|
69
|
+
return emptyArray()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!FrameValidator.isValidYuv(frame)) {
|
|
73
|
+
Log.e(TAG, "❌ Invalid frame provided for detection.")
|
|
74
|
+
return emptyArray()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
val input = inputBuffer ?: return emptyArray()
|
|
78
|
+
|
|
79
|
+
synchronized(lock) {
|
|
80
|
+
input.rewind()
|
|
81
|
+
|
|
82
|
+
fillInputFromYuvFrame(
|
|
83
|
+
frame = frame,
|
|
84
|
+
input = input,
|
|
85
|
+
dstWidth = inputWidth,
|
|
86
|
+
dstHeight = inputHeight,
|
|
87
|
+
dataType = inputDataType ?: DataType.FLOAT32
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
val output = Array(1) { Array(300) { FloatArray(6) } }
|
|
91
|
+
|
|
92
|
+
localInterpreter.run(input, output)
|
|
93
|
+
|
|
94
|
+
val detections = parseNmsOutput(output, confidenceThreshold = 0.5f)
|
|
95
|
+
|
|
96
|
+
return detections.toTypedArray()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override fun close() {
|
|
101
|
+
interpreter?.close()
|
|
102
|
+
interpreter = null
|
|
103
|
+
inputBuffer = null
|
|
104
|
+
Log.d(TAG, "🧹 YOLO model disposed")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun parseNmsOutput(
|
|
108
|
+
output: Array<Array<FloatArray>>,
|
|
109
|
+
confidenceThreshold: Float = 0.5f
|
|
110
|
+
): List<Detection> {
|
|
111
|
+
val detections = mutableListOf<Detection>()
|
|
112
|
+
|
|
113
|
+
for (row in output[0]) {
|
|
114
|
+
val score = row[4]
|
|
115
|
+
if (score < confidenceThreshold) continue
|
|
116
|
+
|
|
117
|
+
detections.add(
|
|
118
|
+
Detection(
|
|
119
|
+
boundingBox = BoundingBox(
|
|
120
|
+
x1 = row[0].toDouble(),
|
|
121
|
+
y1 = row[1].toDouble(),
|
|
122
|
+
x2 = row[2].toDouble(),
|
|
123
|
+
y2 = row[3].toDouble()
|
|
124
|
+
),
|
|
125
|
+
score = score.toDouble(),
|
|
126
|
+
classId = row[5].toDouble()
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return detections
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private fun fillInputFromYuvFrame(
|
|
135
|
+
frame: HybridFrameSpec,
|
|
136
|
+
input: ByteBuffer,
|
|
137
|
+
dstWidth: Int,
|
|
138
|
+
dstHeight: Int,
|
|
139
|
+
dataType: DataType
|
|
140
|
+
) {
|
|
141
|
+
val srcWidth = frame.width.toInt()
|
|
142
|
+
val srcHeight = frame.height.toInt()
|
|
143
|
+
|
|
144
|
+
val planes = frame.getPlanes()
|
|
145
|
+
|
|
146
|
+
val yPlane = planes[0]
|
|
147
|
+
val uPlane = planes[1]
|
|
148
|
+
val vPlane = planes[2]
|
|
149
|
+
|
|
150
|
+
val yBytes = yPlane.getPixelBuffer().toByteArray()
|
|
151
|
+
val uBytes = uPlane.getPixelBuffer().toByteArray()
|
|
152
|
+
val vBytes = vPlane.getPixelBuffer().toByteArray()
|
|
153
|
+
|
|
154
|
+
val yRowStride = yPlane.bytesPerRow.roundToInt()
|
|
155
|
+
val uRowStride = uPlane.bytesPerRow.roundToInt()
|
|
156
|
+
val vRowStride = vPlane.bytesPerRow.roundToInt()
|
|
157
|
+
|
|
158
|
+
input.rewind()
|
|
159
|
+
|
|
160
|
+
for (dy in 0 until dstHeight) {
|
|
161
|
+
for (dx in 0 until dstWidth) {
|
|
162
|
+
val (srcX, srcY) = mapModelPixelToFramePixel(
|
|
163
|
+
dx = dx,
|
|
164
|
+
dy = dy,
|
|
165
|
+
dstWidth = dstWidth,
|
|
166
|
+
dstHeight = dstHeight,
|
|
167
|
+
srcWidth = srcWidth,
|
|
168
|
+
srcHeight = srcHeight,
|
|
169
|
+
orientation = frame.orientation
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
val yIndex = srcY * yRowStride + srcX
|
|
173
|
+
|
|
174
|
+
val uvX = srcX / 2
|
|
175
|
+
val uvY = srcY / 2
|
|
176
|
+
|
|
177
|
+
val uIndex = uvY * uRowStride + uvX * 2
|
|
178
|
+
val vIndex = uvY * vRowStride + uvX * 2
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
yIndex >= yBytes.size ||
|
|
182
|
+
uIndex >= uBytes.size ||
|
|
183
|
+
vIndex >= vBytes.size
|
|
184
|
+
) {
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
val y = yBytes[yIndex].toInt() and 0xFF
|
|
189
|
+
val u = uBytes[uIndex].toInt() and 0xFF
|
|
190
|
+
val v = vBytes[vIndex].toInt() and 0xFF
|
|
191
|
+
|
|
192
|
+
val rFloat = y + 1.402f * (v - 128)
|
|
193
|
+
val gFloat = y - 0.344136f * (u - 128) - 0.714136f * (v - 128)
|
|
194
|
+
val bFloat = y + 1.772f * (u - 128)
|
|
195
|
+
|
|
196
|
+
val r = rFloat.roundToInt().coerceIn(0, 255)
|
|
197
|
+
val g = gFloat.roundToInt().coerceIn(0, 255)
|
|
198
|
+
val b = bFloat.roundToInt().coerceIn(0, 255)
|
|
199
|
+
|
|
200
|
+
when (dataType) {
|
|
201
|
+
DataType.FLOAT32 -> {
|
|
202
|
+
input.putFloat(r / 255f)
|
|
203
|
+
input.putFloat(g / 255f)
|
|
204
|
+
input.putFloat(b / 255f)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
DataType.UINT8 -> {
|
|
208
|
+
input.put(r.toByte())
|
|
209
|
+
input.put(g.toByte())
|
|
210
|
+
input.put(b.toByte())
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
else -> error("Unsupported input type: $dataType")
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
input.rewind()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private fun mapModelPixelToFramePixel(
|
|
222
|
+
dx: Int,
|
|
223
|
+
dy: Int,
|
|
224
|
+
dstWidth: Int,
|
|
225
|
+
dstHeight: Int,
|
|
226
|
+
srcWidth: Int,
|
|
227
|
+
srcHeight: Int,
|
|
228
|
+
orientation: CameraOrientation
|
|
229
|
+
): Pair<Int, Int> {
|
|
230
|
+
val nx = dx.toFloat() / dstWidth
|
|
231
|
+
val ny = dy.toFloat() / dstHeight
|
|
232
|
+
|
|
233
|
+
return when (orientation) {
|
|
234
|
+
CameraOrientation.UP -> {
|
|
235
|
+
val srcX = (nx * srcWidth).toInt()
|
|
236
|
+
val srcY = (ny * srcHeight).toInt()
|
|
237
|
+
srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
CameraOrientation.DOWN -> {
|
|
241
|
+
val srcX = ((1f - nx) * srcWidth).toInt()
|
|
242
|
+
val srcY = ((1f - ny) * srcHeight).toInt()
|
|
243
|
+
srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
CameraOrientation.LEFT -> {
|
|
247
|
+
val srcX = (ny * srcWidth).toInt()
|
|
248
|
+
val srcY = ((1f - nx) * srcHeight).toInt()
|
|
249
|
+
srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
CameraOrientation.RIGHT -> {
|
|
253
|
+
val srcX = ((1f - ny) * srcWidth).toInt()
|
|
254
|
+
val srcY = (nx * srcHeight).toInt()
|
|
255
|
+
srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
|
|
|
4
4
|
import android.graphics.BitmapFactory
|
|
5
5
|
import android.graphics.Matrix
|
|
6
6
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
7
|
+
import com.margelo.nitro.camera.CameraOrientation
|
|
7
8
|
import java.io.ByteArrayOutputStream
|
|
8
9
|
|
|
9
10
|
|
|
@@ -20,7 +21,13 @@ object BitmapOrientationFixer {
|
|
|
20
21
|
jpegBytes.size
|
|
21
22
|
) ?: return jpegBytes
|
|
22
23
|
|
|
23
|
-
val rotationDegrees =
|
|
24
|
+
val rotationDegrees = when (frame.orientation) {
|
|
25
|
+
CameraOrientation.LEFT -> 90f
|
|
26
|
+
CameraOrientation.RIGHT -> 270f
|
|
27
|
+
CameraOrientation.UP -> 0f
|
|
28
|
+
CameraOrientation.DOWN -> 180f
|
|
29
|
+
else -> 0f
|
|
30
|
+
}
|
|
24
31
|
|
|
25
32
|
val matrix = Matrix().apply {
|
|
26
33
|
postRotate(rotationDegrees)
|
package/ios/HybridYolo.swift
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
|
-
//
|
|
2
|
-
// HybridYolo.swift
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by Khaoula-Ghalimi on 22/06/2026.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
1
|
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import VisionCamera
|
|
9
4
|
|
|
10
|
-
class HybridYolo: HybridYoloSpec {
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
public class HybridYolo: HybridYoloSpec {
|
|
6
|
+
private static let tag = "YOLO_TAG"
|
|
7
|
+
|
|
8
|
+
// Initialisateur obligatoire pour les modules Nitro
|
|
9
|
+
public required init() {
|
|
10
|
+
super.init()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Charge l'objet modèle YOLO spécifié par son chemin.
|
|
15
|
+
* Implémente la méthode obligatoire du protocole Nitro TypeScript.
|
|
16
|
+
*/
|
|
17
|
+
public override func loadModel(modelPath: String) throws -> any HybridYoloModelSpec {
|
|
18
|
+
NSLog("[%@]: Trying to load model object: %@", HybridYolo.tag, modelPath)
|
|
19
|
+
|
|
20
|
+
// Initialise et retourne votre sous-classe de modèle Nitro (assurez-vous qu'elle s'appelle bien HybridYoloModel)
|
|
21
|
+
return try HybridYoloModel(modelPath: modelPath)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Valide un Frame de la caméra, le convertit en JPEG permanent (NV12 -> JPEG -> Fix Rotation),
|
|
26
|
+
* puis l'encode instantanément en chaîne de caractères Base64 standard.
|
|
27
|
+
*/
|
|
28
|
+
public override func frameToBase64(frame: any HybridFrameSpec) throws -> String {
|
|
29
|
+
NSLog("[%@]: Trying to convert frame to base64", HybridYolo.tag)
|
|
30
|
+
|
|
31
|
+
do {
|
|
32
|
+
// 1. Validation du buffer vidéo via notre validateur adapté à iOS (NV12 à 2 plans minimum)
|
|
33
|
+
guard FrameValidator.isValidYuv(frame: frame) else {
|
|
34
|
+
return ""
|
|
35
|
+
}
|
|
36
|
+
NSLog("[%@]: frameToBase64: frame is valid YUV", HybridYolo.tag)
|
|
37
|
+
|
|
38
|
+
// 2. Traitement complet de l'image (Conversion NV12 -> Encodage GPU JPEG -> Rendu de sécurité)
|
|
39
|
+
let jpegBytes = FrameJpegConverter.toJpegBytes(frame: frame, quality: 80)
|
|
40
|
+
|
|
41
|
+
guard !jpegBytes.isEmpty else {
|
|
42
|
+
NSLog("[%@]: ❌ frameToBase64 failed: converted JPEG bytes array is empty", HybridYolo.tag)
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
NSLog("[%@]: frameToBase64: jpegBytes size: %d bytes", HybridYolo.tag, jpegBytes.count)
|
|
46
|
+
|
|
47
|
+
// 3. Encapsulation dans un conteneur Data pour un encodage Base64 matériel (sans sauts de ligne, équivalent de NO_WRAP)
|
|
48
|
+
let data = Data(jpegBytes)
|
|
49
|
+
return data.base64EncodedString(options: [])
|
|
50
|
+
|
|
51
|
+
} catch {
|
|
52
|
+
NSLog("[%@]: ❌ frameToBase64 failed: %@", HybridYolo.tag, error.localizedDescription)
|
|
53
|
+
return ""
|
|
54
|
+
}
|
|
13
55
|
}
|
|
14
56
|
}
|