diff --git a/app/build.gradle b/app/build.gradle index 2b41740..05c531f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,10 +49,14 @@ dependencies { implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.rmtheis:tess-two:9.1.0' - implementation files('libs/camerautil.jar') implementation project(path: ':opencv') implementation files('libs/zxingcpp-release.aar') + implementation files('libs/Yolov5-tflite-Detector.aar') testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + //开源项目使用的库 + implementation 'org.tensorflow:tensorflow-lite:2.4.0' + implementation 'org.tensorflow:tensorflow-lite-gpu:2.4.0' + implementation 'com.google.code.gson:gson:2.10.1' } \ No newline at end of file diff --git a/app/libs/Yolov5-tflite-Detector.aar b/app/libs/Yolov5-tflite-Detector.aar new file mode 100644 index 0000000..15926a3 Binary files /dev/null and b/app/libs/Yolov5-tflite-Detector.aar differ diff --git a/app/libs/camerautil.jar b/app/libs/camerautil.jar deleted file mode 100644 index c09ee81..0000000 Binary files a/app/libs/camerautil.jar and /dev/null differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bdc26cf..61f4499 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,6 @@ - @@ -37,7 +36,8 @@ android:supportsRtl="true" android:theme="@style/Theme.MainCar" android:usesCleartextTraffic="true" - tools:targetApi="31"> + tools:targetApi="31" + tools:replace="android:label"> diff --git a/app/src/main/assets/TSclass.txt b/app/src/main/assets/TSclass.txt new file mode 100644 index 0000000..d72367e --- /dev/null +++ b/app/src/main/assets/TSclass.txt @@ -0,0 +1,6 @@ +go_straight +no_turn +turn_around +turn_left +turn_right +no_straight diff --git a/app/src/main/assets/TSyolov5s-fp16-3.tflite b/app/src/main/assets/TSyolov5s-fp16-3.tflite new file mode 100644 index 0000000..50eea9c Binary files /dev/null and b/app/src/main/assets/TSyolov5s-fp16-3.tflite differ diff --git a/app/src/main/assets/TSyolov5s-fp16-byGray.tflite b/app/src/main/assets/TSyolov5s-fp16-byGray.tflite new file mode 100644 index 0000000..5b54d41 Binary files /dev/null and b/app/src/main/assets/TSyolov5s-fp16-byGray.tflite differ diff --git a/app/src/main/assets/TSyolov5s-fp16.tflite b/app/src/main/assets/TSyolov5s-fp16.tflite new file mode 100644 index 0000000..05366fb Binary files /dev/null and b/app/src/main/assets/TSyolov5s-fp16.tflite differ diff --git a/app/src/main/assets/VIDclass.txt b/app/src/main/assets/VIDclass.txt new file mode 100644 index 0000000..d326132 --- /dev/null +++ b/app/src/main/assets/VIDclass.txt @@ -0,0 +1,6 @@ +bike +motor +car +truck +van +bus diff --git a/app/src/main/assets/VIDyolov5s-fp16-2.tflite b/app/src/main/assets/VIDyolov5s-fp16-2.tflite new file mode 100644 index 0000000..ecdf696 Binary files /dev/null and b/app/src/main/assets/VIDyolov5s-fp16-2.tflite differ diff --git a/app/src/main/assets/VIDyolov5s-fp16.tflite b/app/src/main/assets/VIDyolov5s-fp16.tflite new file mode 100644 index 0000000..1576a0a Binary files /dev/null and b/app/src/main/assets/VIDyolov5s-fp16.tflite differ diff --git a/app/src/main/java/com/uns/maincar/constants/GlobalColor.java b/app/src/main/java/com/uns/maincar/constants/GlobalColor.java index e267dff..1689c4d 100644 --- a/app/src/main/java/com/uns/maincar/constants/GlobalColor.java +++ b/app/src/main/java/com/uns/maincar/constants/GlobalColor.java @@ -15,5 +15,6 @@ public enum GlobalColor PURPLE, CYAN, BLACK, - WHITE + WHITE, + INVALIDATE } diff --git a/app/src/main/java/com/uns/maincar/constants/GlobalShape.java b/app/src/main/java/com/uns/maincar/constants/GlobalShape.java index b7732c4..87d065a 100644 --- a/app/src/main/java/com/uns/maincar/constants/GlobalShape.java +++ b/app/src/main/java/com/uns/maincar/constants/GlobalShape.java @@ -12,5 +12,6 @@ public enum GlobalShape CIRCLE, SQUARE, TRIANGLE, - RECTANGLE + RECTANGLE, + INVALIDATE } diff --git a/app/src/main/java/com/uns/maincar/data_type/ShapeColorResult.java b/app/src/main/java/com/uns/maincar/data_type/ShapeColorResult.java new file mode 100644 index 0000000..af0e21d --- /dev/null +++ b/app/src/main/java/com/uns/maincar/data_type/ShapeColorResult.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023. UnknownNetworkService Group + * This file is created by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.data_type; + +import androidx.annotation.NonNull; + +import com.uns.maincar.constants.GlobalColor; +import com.uns.maincar.constants.GlobalShape; + +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +public class ShapeColorResult +{ + private final HashMap> storage; + + public ShapeColorResult() + { + storage = new HashMap<>(); + storage.put(GlobalColor.RED, new HashMap<>()); + storage.put(GlobalColor.GREEN, new HashMap<>()); + storage.put(GlobalColor.BLUE, new HashMap<>()); + storage.put(GlobalColor.YELLOW, new HashMap<>()); + storage.put(GlobalColor.PURPLE, new HashMap<>()); + storage.put(GlobalColor.CYAN, new HashMap<>()); + storage.forEach((k, v) -> + { + v.put(GlobalShape.STAR, 0); + v.put(GlobalShape.CIRCLE, 0); + v.put(GlobalShape.SQUARE, 0); + v.put(GlobalShape.TRIANGLE, 0); + v.put(GlobalShape.RECTANGLE, 0); + }); + } + + public void SetValue(GlobalColor color, GlobalShape shape, int value) + { + try + { + if (storage.containsKey(color) && Objects.requireNonNull(storage.get(color)).containsKey(shape)) + Objects.requireNonNull(storage.get(color)).replace(shape, value); + } + catch (Exception ignored) + { + + } + } + + public int GetValue(GlobalColor color, GlobalShape shape) + { + try + { + if (storage.containsKey(color) && Objects.requireNonNull(storage.get(color)).containsKey(shape)) + return Objects.requireNonNull(Objects.requireNonNull(storage.get(color)).get(shape)); + else + return -1; + } + catch (Exception e) + { + return -2; + } + } + + public int AllItemCount() + { + AtomicInteger sum = new AtomicInteger(); + storage.forEach((k, v) -> v.forEach((s_k, s_v) -> sum.addAndGet(s_v))); + return sum.get(); + } + + public void Clear() + { + storage.clear(); + storage.put(GlobalColor.RED, new HashMap<>()); + storage.put(GlobalColor.GREEN, new HashMap<>()); + storage.put(GlobalColor.BLUE, new HashMap<>()); + storage.put(GlobalColor.YELLOW, new HashMap<>()); + storage.put(GlobalColor.PURPLE, new HashMap<>()); + storage.put(GlobalColor.CYAN, new HashMap<>()); + storage.forEach((k, v) -> + { + v.put(GlobalShape.STAR, 0); + v.put(GlobalShape.CIRCLE, 0); + v.put(GlobalShape.SQUARE, 0); + v.put(GlobalShape.TRIANGLE, 0); + v.put(GlobalShape.RECTANGLE, 0); + }); + } + + @NonNull + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("--------------------ShapeColorResult START--------------------\n"); + sb.append("Total Item Count: ").append(AllItemCount()).append("\n"); + storage.forEach((k, v) -> v.forEach((s_k, s_v) -> sb.append("storage[").append(k).append("][").append(s_k).append("] = ").append(s_v).append("\n"))); + sb.append("---------------------ShapeColorResult END---------------------\n"); + return sb.toString(); + } +} diff --git a/app/src/main/java/com/uns/maincar/gui/MainActivity.java b/app/src/main/java/com/uns/maincar/gui/MainActivity.java index 720fd27..5542950 100644 --- a/app/src/main/java/com/uns/maincar/gui/MainActivity.java +++ b/app/src/main/java/com/uns/maincar/gui/MainActivity.java @@ -45,6 +45,10 @@ import com.uns.maincar.cpp_interface.QRDecoder; import com.uns.maincar.cpp_interface.ShapeColor; import com.uns.maincar.cpp_interface.TrafficLight; import com.uns.maincar.cpp_interface.TrafficSign; +import com.uns.maincar.data_type.ShapeColorResult; +import com.uns.maincar.open_source.shape.ShapeDetector; +import com.uns.maincar.open_source.traffic_sign.YoloV5_tfLite_TSDetector; +import com.uns.maincar.open_source.vehicle.YoloV5_tfLite_VIDDetector; import com.uns.maincar.tools.ImageReleaser; import com.uns.maincar.tools.OCRDataReleaser; import com.uns.maincar.tools.TextFilter; @@ -87,6 +91,10 @@ public class MainActivity extends AppCompatActivity private final String SerialPortPath = "/dev/ttyS4"; //通信通道标志,true为Wifi,false为串口 private final boolean CommunicationUsingWifi = true; + //Yolo_tfLite检测模型对象 - 交通标志 - 来自开源项目 + private static final YoloV5_tfLite_TSDetector TS_Detector = new YoloV5_tfLite_TSDetector(); + //Yolo_tfLite检测模型对象 - 车型 - 来自于开源项目 + private static final YoloV5_tfLite_VIDDetector VID_Detector = new YoloV5_tfLite_VIDDetector(); //调试用16进制数组 private final String[] byte_str = { "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07", "0x08", "0x09", "0x0A", "0x0B", "0x0C", "0x0D", "0x0E", "0x0F", @@ -614,6 +622,26 @@ public class MainActivity extends AppCompatActivity dtc_client.CloseConnection(); //关闭通信 throw new NullPointerException(); //通过异常来崩溃。 }); + + context.findViewById(R.id.btn_os_shapecolor).setOnClickListener(view -> + { + ShapeDetector detector = new ShapeDetector(); + detector.shapePicProcess(currImage); + ShapeColorResult result = detector.GetAllResult(); + ToastLog(result.toString(), false, false); + }); + + context.findViewById(R.id.btn_os_trafficsign).setOnClickListener(view -> + { + String res = TS_Detector.processImage(currImage); + ToastLog("Traffic Sign Result: " + res, false, false); + }); + + context.findViewById(R.id.btn_os_vehicle).setOnClickListener(view -> + { + String res = TS_Detector.processImage(currImage); + ToastLog("Vehicle Result: " + res, false, false); + }); } //----------------------------------------到此处终止---------------------------------------- @@ -747,6 +775,14 @@ public class MainActivity extends AppCompatActivity //二维码扫描自检 ToastLog(QRDecoder.SelfTest(BitmapFactory.decodeResource(getResources(), R.drawable.qr_decode_test)), false, false); + //初始化开源交通标志识别库 + YoloV5_tfLite_TSDetector.minimumConfidence = 0.7f; + ToastLog("Open Source Traffic Sign Detector: " + (TS_Detector.LoadModel("CPU", 4, this.getAssets()) ? "Success" : "Failure"), false, false); + + //初始化开源车型识别库 + YoloV5_tfLite_VIDDetector.minimumConfidence = 0.7f; + ToastLog("Open Source Vehicle Detector: " + (VID_Detector.LoadModel("CPU", 4, this.getAssets()) ? "Success" : "Failure"), false, false); + //获取主车IP地址 wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); dhcpInfo = wifiManager.getDhcpInfo(); diff --git a/app/src/main/java/com/uns/maincar/open_source/shape/ShapeDetector.java b/app/src/main/java/com/uns/maincar/open_source/shape/ShapeDetector.java new file mode 100644 index 0000000..f971193 --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/shape/ShapeDetector.java @@ -0,0 +1,444 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + * Value/Result Interface changed by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.shape; + +import android.graphics.Bitmap; +import android.util.Log; + +import com.uns.maincar.constants.GlobalColor; +import com.uns.maincar.constants.GlobalShape; +import com.uns.maincar.data_type.ShapeColorResult; +import com.uns.maincar.open_source.utils.BitmapProcess; +import com.uns.maincar.open_source.utils.ColorHSV; +import com.uns.maincar.open_source.utils.ShapeStatistics; + +import org.opencv.android.Utils; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.MatOfPoint2f; +import org.opencv.core.Point; +import org.opencv.core.RotatedRect; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ShapeDetector +{ + //目标类的简写名称 + private static final String TAG = ShapeDetector.class.getSimpleName(); + //轮廓绘制/轮廓统计 + private static final List contours = new ArrayList<>(); + //HashMap<颜色,HashMap<形状,数量>> + private final HashMap ColorCounts = new HashMap<>(); + //检测出的所有图形数量 + private int totals = 0; + //使用枚举类型的结果储存 + ShapeColorResult result = new ShapeColorResult(); + + public int GetSpecItemCount(GlobalColor color, GlobalShape shape) + { + return result.GetValue(color, shape); + } + + public int GetAllItemCount() + { + return result.AllItemCount(); + } + + public ShapeColorResult GetAllResult() + { + return result; + } + + /** + * 获取该图片中所有的图形数量 + */ + public int getTotals() + { + for (Map.Entry map : ColorCounts.entrySet()) + { + totals += map.getValue().getCounts("总计"); + } + return totals; + } + + /** + * 重置统计 + * + * @param totals 0 + */ + public void setTotals(int totals) + { + this.totals = totals; + } + + /** + * 获取指定颜色的统计对象 + * + * @return 该颜色的统计对象<形状, 数量> + */ + public HashMap getColorCounts() + { + return ColorCounts; + } + + /** + * 获取指定图形的数量 + */ + public int getShapeCounts(String shapeName) + { + int counts = 0; + for (Map.Entry map : ColorCounts.entrySet()) + { + counts += map.getValue().getCounts(shapeName); + } + return counts; + } + + /** + * 形状识别 - Bitmap图片处理 + * + * @param inputBitmap 需要处理的图片 + */ + public void shapePicProcess(Bitmap inputBitmap) + { + if (inputBitmap == null) return; + /* openCV创建用来存储图像信息的内存对象 */ + Mat srcMat = new Mat(); + /* 转化为Mat对象 */ + Utils.bitmapToMat(inputBitmap, srcMat); + shapePicProcess(srcMat); + } + + /** + * 形状识别 - Mat图片处理 + * + * @param srcMat 需要识别的图片 + */ + public void shapePicProcess(Mat srcMat) + { + if (srcMat == null) + return; + ColorCounts.clear(); + /* 保存用 */ + BitmapProcess.saveBitmap("TFTAutoCutter", srcMat); + /* 颜色形状分析 */ + Identify(srcMat, ColorHSV.yellowHSV1, "黄色"); + Identify(srcMat, ColorHSV.greenHSV1, "绿色"); + Identify(srcMat, ColorHSV.cyanHSV, "青色"); + Identify(srcMat, ColorHSV.blueHSV3, "蓝色"); + Identify(srcMat, ColorHSV.purpleHSV2, "紫色"); + /* 红色颜色取反,方便处理 */ + Identify(srcMat, "红色"); + } + + /** + *

形状识别 - 反色处理

+ *

因红色阈值问题,建议将图片进行反色处理

+ * + * @param inputMat 已经处理的Mat对象 + */ + private void Identify(Mat inputMat, @SuppressWarnings("SameParameterValue") String colorName) + { + Mat dstMat = new Mat(); + //RGB转换为BGR - 红蓝色互换 + Imgproc.cvtColor(inputMat, dstMat, Imgproc.COLOR_BGR2RGB); + Identify(dstMat, ColorHSV.red2blueHSV, colorName); + } + + /** + * 形状识别 + * + * @param Mtmp 已经处理的Mat对象 + * @param r 色彩数据 + * @param colorName 色彩名 + */ + private void Identify(Mat Mtmp, int[] r, String colorName) + { + /* openCV创建用来存储图像信息的内存对象 */ + Mat hsvMat = new Mat(); + Mat outMat = new Mat(); + Mat mat = Mtmp.clone(); + /* 转换为HSV */ + Imgproc.cvtColor(mat, hsvMat, Imgproc.COLOR_RGB2HSV); + /* 颜色分割 */ + Core.inRange(hsvMat, new Scalar(r[2], r[4], r[6]), new Scalar(r[1], r[3], r[5]), hsvMat); + /* 确定运算核,类似于卷积核 */ + Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); + /* 开运算(除去白噪点) */ + Imgproc.morphologyEx(hsvMat, hsvMat, Imgproc.MORPH_OPEN, kernel); + /* 闭运算(除去黑噪点) */ +// Imgproc.morphologyEx(hsvMat, hsvMat, Imgproc.MORPH_CLOSE, kernel); + /* 轮廓提取,用于提取图像的轮廓 */ + contours.clear(); + Imgproc.findContours(hsvMat, contours, outMat, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); + /* 绘制轮廓,用于绘制找到的图像轮廓 */ + /* + * 函数参数详解: + * 第一个参数image表示目标图像 + * 第二个参数contours表示输⼊的轮廓组,每⼀组轮廓由点vector构成 + * 第三个参数contourIdx指明画第⼏个轮廓,如果该参数为负值,则画全部轮廓 + * 第四个参数color为轮廓的颜色 + * 第五个参数thickness为轮廓的线宽,如果为负值或CV_FILLED表⽰填充轮廓内部 + */ + Imgproc.drawContours(mat, contours, -1, new Scalar(0, 255, 0), 2); + /* 形状统计 */ + /* 核心统计代码,参数已调整 */ + //轮廓 + MatOfPoint2f contour2f; + //近似曲线(多边形拟合) + MatOfPoint2f approxCurve; + /* 逼近的精度(阈值),设定的原始曲线与近似曲线之间的最大距离 */ + double epsilon; + int tri, rect, circle, star, rhombus; + tri = rect = circle = star = rhombus = 0; + Log.e(TAG, "----------" + colorName + "总计轮廓: " + contours.size() + "----------"); + /* 遍历轮廓 */ + for (int i = 0; i < contours.size(); i++) + { + /* 判断面积是否大于阈值(有效图形) */ + if (Imgproc.contourArea(contours.get(i)) > 200) + { + Log.i(TAG, "查找到有效轮廓,面积为: " + Imgproc.contourArea(contours.get(i))); + /* 某一个点的集合(当前对象的轮廓) */ + contour2f = new MatOfPoint2f(contours.get(i).toArray()); + /* + * 计算轮廓的周长 + * 0.035这个系数是一个精度因子,用来控制近似多边形的形状。 + * 它越小,近似多边形就越接近原始轮廓。它越大,近似多边形就越简单,有更少的顶点。你可以根据你的需要调整这个系数。 + * by New Bing + */ + epsilon = 0.045 * Imgproc.arcLength(contour2f, true); + //多边形拟合后的轮廓 + approxCurve = new MatOfPoint2f(); + /* 多边形拟合 */ + Imgproc.approxPolyDP(contour2f, approxCurve, epsilon, true); + /* boundingRect获取不带旋转角度的最小外接矩形 */ +// Rect minRect = Imgproc.boundingRect(approxCurve); + /* 绘制不带旋转角度的外接矩形,并计算外接矩形的轮廓中心 */ +// Imgproc.rectangle(mat, minRect, new Scalar(255, 255, 0), 1); + /* minAreaRect获得带旋转角度的最小外接矩形 */ + RotatedRect minRotatedRect = Imgproc.minAreaRect(approxCurve); + /* 获取顶点数据 */ + Point[] box = new Point[4]; + minRotatedRect.points(box); + /* 将顶点转换为整数类型 */ + MatOfPoint boxInt = new MatOfPoint(); + boxInt.fromArray(box); + /* 绘制带旋转角度的外接矩形,并计算外接矩形的轮廓中心 */ + Imgproc.polylines(mat, Collections.singletonList(boxInt), true, new Scalar(255, 255, 255)); + Log.i(TAG, "包含角点数: " + approxCurve.rows()); + if (approxCurve.rows() == 3) tri++; + /* 判断矩形和菱形 */ + /* 面积判断法 - 旧 */ +// else if (approxCurve.rows() == 4) { +// double area, minArea; +// /* 该图形(四边形)拟合的面积 */ +// area = Imgproc.contourArea(approxCurve); +// /* 包含旋转角度的最小外接矩形的面积 */ +// RotatedRect minAreaRect = Imgproc.minAreaRect(approxCurve); +// minArea = minAreaRect.size.area(); +// /* 图形面积/外接矩形面积 */ +// double rec = area / minArea; +// Log.i(TAG, "这是area / minArea得到的阈值: " + rec); +// if (rec >= 0.80 && rec < 1.15) rect++; +// else rhombus++; +// } + /* 判断菱形 - 边长 */ + else if (isRhombus(approxCurve)) rhombus++; + /* 判断矩形 - 对角线 */ + else if (isRectangle(approxCurve)) rect++; + /* 判断五角星和圆形 - 面积 */ +// else if (approxCurve.rows() > 4) { +// /* 最小外接矩形的面积 */ +// int minAreaRect = minRect.height * minRect.width; +// /* 该图形面积 */ +// double area = Imgproc.contourArea(contours.get(i)); +// if ((area / minAreaRect) > 0.5) circle++; +// else star++; +// } + /* 判断五角星和圆形 - 圆形度 */ + else if (approxCurve.rows() > 4) + { + /* 该图形面积 */ + double area = Imgproc.contourArea(contours.get(i)); + /* 该图形周长 */ + double len = Imgproc.arcLength(approxCurve, true); + /* 圆形度 */ + double roundness = (4 * Math.PI * area) / (len * len); + Log.i(TAG, "该图形的圆形度: " + roundness); + if (roundness > 0.8) circle++; + else star++; + } + } + } + /* 引用ShapeCount对象存放识别数据 */ + //SaveResult(colorName, circle, tri, rect, star, rhombus); + + //使用枚举类型的数据存储,这样更便于查询 + BetterSaveResult(colorName, circle, tri, rect, star, rhombus); + /* 输出结果 */ + String msg = "圆形: " + circle + " 三角形: " + tri + " 矩形: " + rect + " 菱形: " + rhombus + " 五角星: " + star; + Log.e(TAG, msg); + /* 保存图片 */ + BitmapProcess.saveBitmap(colorName, mat); + Log.e(TAG, "----------" + colorName + "识别完成----------"); + } + + /** + * 判断一个多边形是否为菱形 + * + * @param approxCurve MatOfPoint2f + * @return boolean + */ + private static boolean isRhombus(MatOfPoint2f approxCurve) + { + /* 如果顶点数为4,则可能为菱形 */ + if (approxCurve.toArray().length == 4) + { + /* 获取顶点坐标 */ + Point[] points = approxCurve.toArray(); + /* 获取边长长度 */ + double l1 = getDistance(points[0], points[1]); + double l2 = getDistance(points[1], points[2]); + double l3 = getDistance(points[2], points[3]); + double l4 = getDistance(points[3], points[0]); + Log.i(TAG, "该轮廓四边边长(顺时针):\n" + "■■■" + l1 + "■■■" + l2 + "■■■\n■■■" + l4 + "■■■" + l3 + "■■■"); + /* 轮廓的邻边边长在误差范围内相等则为菱形 */ + return Math.abs(l1 - l2) < 5 && Math.abs(l2 - l3) < 5 && Math.abs(l3 - l4) < 5 && Math.abs(l4 - l1) < 5; + } + // 不是菱形 + return false; + } + + /** + * 判断一个多边形是否为矩形 + * + * @param approxCurve MatOfPoint2f + * @return boolean + */ + private static boolean isRectangle(MatOfPoint2f approxCurve) + { + /* 如果顶点数为4,则可能为矩形 */ + if (approxCurve.toArray().length == 4) + { + /* 计算四个顶点之间的距离 */ +// double d1 = getDistance(approxCurve.toArray()[0], approxCurve.toArray()[1]); +// double d2 = getDistance(approxCurve.toArray()[1], approxCurve.toArray()[2]); +// double d3 = getDistance(approxCurve.toArray()[2], approxCurve.toArray()[3]); +// double d4 = getDistance(approxCurve.toArray()[3], approxCurve.toArray()[0]); + /* 计算对角线之间的距离 */ + double d5 = getDistance(approxCurve.toArray()[0], approxCurve.toArray()[2]); + double d6 = getDistance(approxCurve.toArray()[1], approxCurve.toArray()[3]); + /* 判断对角线是否相等,并且相邻边是否垂直(即乘积为零) */ +// double vector = Math.abs(d1 * d2 + d2 * d3 + d3 * d4 + d4 * d1); +// Log.e(TAG, "对角线长度比对: " + Math.abs(d5 - d6) + "邻边角度误差: " + vector); +// return Math.abs(d5 - d6) < 1e-6 && vector < 1e-6; // 是矩形 + /* 判断对角线是否相等 */ + Log.e(TAG, "对角线长度比对: " + Math.abs(d5 - d6)); + return Math.abs(d5 - d6) < 3; // 是矩形 + } + // 不是矩形 + return false; + } + + /** + * 计算两点之间的距离 + * + * @param p1 - + * @param p2 - + * @return 两点距离 + */ + private static double getDistance(Point p1, Point p2) + { + return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); + } + + /** + * 形状数据保存 + * + * @param colorName 形状颜色 + * @param circle - + * @param tri - + * @param rect - + * @param star - + * @param rhombus - + */ + /*private void SaveResult(String colorName, int circle, int tri, int rect, int star, int rhombus) + { + *//* 保存该颜色包含的图形统计 *//* + HashMap hashMap = new HashMap<>(); + hashMap.put("三角形", tri); + hashMap.put("矩形", rect); + hashMap.put("菱形", rhombus); + hashMap.put("五角星", star); + hashMap.put("圆形", circle); + hashMap.put("总计", tri + rect + rhombus + star + circle); + *//* 形状计数对象 *//* + ShapeStatistics statistics = new ShapeStatistics(); + *//* 保存在该对象上 *//* + statistics.setShapeStatistics(hashMap); + ColorCounts.put(colorName, statistics); + }*/ + + /** + * 将字符串颜色名转换为枚举类型 + * + * @param color_name 颜色名称 + */ + private GlobalColor ConvertColorName(String color_name) + { + switch (color_name) + { + case "红色": + return GlobalColor.RED; + case "黄色": + return GlobalColor.YELLOW; + case "绿色": + return GlobalColor.GREEN; + case "青色": + return GlobalColor.CYAN; + case "蓝色": + return GlobalColor.BLUE; + case "紫色": + return GlobalColor.PURPLE; + } + return GlobalColor.INVALIDATE; + } + + /** + * 使用枚举类型的数据保存 + * “我特地重写了这个类的数据保存逻辑,因为直接使用描述字符串作为Key来存储信息实在不是一个好主意。” ——UnknownObject@2023-06-07 + * + * @param colorName 颜色名称 + * @param circle 圆 + * @param tri 三角 + * @param rect 矩形 + * @param star 五角星 + * @param rhombus 菱形 + */ + private void BetterSaveResult(String colorName, int circle, int tri, int rect, int star, int rhombus) + { + GlobalColor color = ConvertColorName(colorName); + if (color != GlobalColor.INVALIDATE) + { + result.SetValue(color, GlobalShape.CIRCLE, circle); + result.SetValue(color, GlobalShape.TRIANGLE, tri); + result.SetValue(color, GlobalShape.RECTANGLE, rect); + result.SetValue(color, GlobalShape.STAR, star); + result.SetValue(color, GlobalShape.SQUARE, rhombus); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uns/maincar/open_source/traffic_sign/YoloV5_tfLite_TSDetector.java b/app/src/main/java/com/uns/maincar/open_source/traffic_sign/YoloV5_tfLite_TSDetector.java new file mode 100644 index 0000000..5e15b22 --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/traffic_sign/YoloV5_tfLite_TSDetector.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.traffic_sign; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.SystemClock; +import android.util.Log; + +import com.google.gson.Gson; +import com.uns.maincar.open_source.utils.BitmapProcess; + +import org.tensorflow.lite.examples.detection.env.Logger; +import org.tensorflow.lite.examples.detection.tflite.Classifier; +import org.tensorflow.lite.examples.detection.tflite.DetectorFactory; +import org.tensorflow.lite.examples.detection.tflite.YoloV5Classifier; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * 使用基于YoloV5-tfLite模型的交通标志物识别 + */ +public class YoloV5_tfLite_TSDetector +{ + + // Which detection model to use: by default uses Tensorflow Object Detection API frozen + // checkpoints. +// enum DetectorMode {TF_OD_API} + + //日志对象 + private static final Logger LOGGER = new Logger(); + //枚举常量 - 检测模式 +// private static final DetectorMode MODE = DetectorMode.TF_OD_API; + public static final float MINIMUM_CONFIDENCE_TF_OD_API = 0.35f; + //最小置信度 + public static float minimumConfidence; + //核心检测对象 + private YoloV5Classifier detector; + //模型列表 + private final String[] models = new String[]{"TSyolov5s-fp16.tflite", "TSyolov5s-fp16-3.tflite", "TSyolov5s-fp16-byGray.tflite"}; + //检测图片 + private Bitmap SaveBitmap; + private long timestamp = 0; + + public Bitmap getSaveBitmap() + { + return SaveBitmap; + } + + /** + * 加载模型配置 + * + * @param device 使用何种硬件加载 + * @param numThreads 使用多少线程加载 + * @param assetManager AssetManager管理对象 + */ + public boolean LoadModel(String device, int numThreads, AssetManager assetManager) + { + + //模型文件 + String modelString = models[2]; + //检测类别(标签) + String labelFilename = "TSclass.txt"; + /* 线程数(不推荐超过9线程数) */ + if (numThreads > 9) numThreads = 4; + LOGGER.i("Changing model to ***" + modelString + "*** device ***" + device + "***"); + + /* Try to load model. */ + /* 尝试加载模型 */ + try + { + detector = DetectorFactory.getDetector(assetManager, modelString, labelFilename); + // Customize the interpreter to the type of device we want to use. + } + catch (IOException e) + { + e.printStackTrace(); + LOGGER.e(e, "Exception in updateActiveModel()"); +// Toast toast = Toast.makeText(FirstActivity.getContext(), "Classifier could not be initialized", Toast.LENGTH_SHORT); + return false; + } + + switch (device) + { + case "GPU": + detector.useGpu(); + break; + case "NNAPI": + detector.useNNAPI(); + break; + default: + detector.useCPU(); + break; + } + /* 设置线程数 */ + detector.setNumThreads(numThreads); + return true; + } + + /** + * 检测图片 + * + * @param inputBitmap - + * @return Gson字符串 + */ + public String processImage(Bitmap inputBitmap) + { + /* 结果列表对象 */ + List recognitions = new LinkedList<>(); + /* 将结果转换成Gson */ + Gson gson = new Gson(); + + if (inputBitmap == null) return gson.toJson(recognitions); + //416*416 + int cropSize = detector.getInputSize(); + System.out.println(cropSize); + + int width = inputBitmap.getWidth(); + int height = inputBitmap.getHeight(); + float scaleWidth = ((float) cropSize) / width; + float scaleHeight = ((float) cropSize) / height; + //矩阵 + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleHeight); + + /* 将输入图片通过矩阵变换得到416*416大小的新图片 */ + Bitmap croppedBitmap = Bitmap.createBitmap(inputBitmap, 0, 0, width, height, matrix, true); + /* 灰度化图像 */ + croppedBitmap = BitmapProcess.GrayscaleImage(croppedBitmap); + /* 设置输出结果图像(在该图像上绘制识别结果) */ + Bitmap draw = croppedBitmap.copy(Bitmap.Config.ARGB_8888, true); + + ++timestamp; + final long currTimestamp = timestamp; + + LOGGER.i("Preparing image " + currTimestamp + " for detection in bg thread."); + /* 利用分类器classifier对图片进行预测分析,得到图片为每个分类的概率. 比较耗时 */ + LOGGER.i("Running detection on image " + currTimestamp); + + final long startTime = SystemClock.uptimeMillis(); + /* 核心检测 */ + final List results = detector.recognizeImage(croppedBitmap); + /* 计算检测时间 */ + long lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime; + /* 检测出多少对象 */ + Log.e("CHECK", "run: " + results.size()); + /* 检测时间 */ + Log.i("Time Spent: ", lastProcessingTimeMs + "ms"); + + /* 筛选通过最低置信度阈值的识别结果 */ + final List mappedRecognitions = new LinkedList<>(); + for (final Classifier.Recognition result : results) + { + final RectF location = result.getLocation(); + if (location != null && result.getConfidence() >= minimumConfidence) + { + result.setLocation(location); + /* 将通过最低置信度的结果添加到新List */ + mappedRecognitions.add(result); + //识别结果 + Log.e("result: ", result.getTitle() + result.getConfidence()); + drawBitmap(result, draw); + } + } + + return gson.toJson(mappedRecognitions.size() > 0 ? mappedRecognitions : recognitions); + } + + private void drawBitmap(Classifier.Recognition result, Bitmap resultBitmap) + { + final Canvas canvas = new Canvas(resultBitmap); + final Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(2.0f); + canvas.drawRect(result.getLocation(), paint); + SaveBitmap = resultBitmap.copy(Bitmap.Config.ARGB_8888, true); + } +} diff --git a/app/src/main/java/com/uns/maincar/open_source/utils/BitmapProcess.java b/app/src/main/java/com/uns/maincar/open_source/utils/BitmapProcess.java new file mode 100644 index 0000000..a5aafaa --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/utils/BitmapProcess.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.utils; + +import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.util.Log; + +import org.opencv.android.Utils; +import org.opencv.core.Mat; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class BitmapProcess +{ + @SuppressLint("StaticFieldLeak") + private static BitmapProcess mInstance; + private Context mContext; + // 指定我们想要存储文件的地址 + public static final String TargetPath = Environment.getExternalStorageDirectory() + "/" + Environment.DIRECTORY_DCIM + "/Tess/"; + // 获取时间 + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.CHINA); + + private BitmapProcess() + { + } + + public static synchronized BitmapProcess getInstance() + { + if (mInstance == null) + { + mInstance = new BitmapProcess(); + } + return mInstance; + } + + public void init(Context context) + { + this.mContext = context.getApplicationContext(); + } + + /** + * 全局使用的图片保存方法 + * + * @param name 图片名 + * @param mat 需要保存的mat + */ + public static void saveBitmap(String name, Mat mat) + { + if (mat == null) return; + Bitmap bm = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888); + Utils.matToBitmap(mat, bm); + saveBitmap(name, bm); + } + + /** + * 全局使用的图片保存方法 + * + * @param name 图片名 + * @param bm 需要保存的Bitmap + * @return 是否保存成功 + */ + public static String saveBitmap(String name, Bitmap bm) + { + if (bm == null) return "错误,没有图片!"; + return Build.VERSION.SDK_INT < 29 ? saveImageOld(name, bm) : mInstance.saveImageNew(name, bm); + } + + /** + * 旧版本Android保存图片方法 + * + * @param name 图片名 + * @param bm 等待保存的Bitmap + * @return 是否保存成功 + */ + private static String saveImageOld(String name, Bitmap bm) + { + Log.d("Save Bitmap", "Ready to save picture"); + StringBuilder append = new StringBuilder().append("Save Path = "); + String str = TargetPath; + Log.d("Save Bitmap", append.append(str).toString()); + if (fileIsExist()) + { + try + { + FileOutputStream saveImgOut = new FileOutputStream(new File(str, name + "-" + format.format(new Date()) + ".jpg")); + bm.compress(Bitmap.CompressFormat.JPEG, 80, saveImgOut); + saveImgOut.flush(); + Log.d("Save Bitmap", "The picture is save to your phone!"); + return "保存完毕!"; + } + catch (IOException ex) + { + ex.printStackTrace(); + return "IOException!"; + } + } + else + { + Log.d("Save Bitmap", "TargetPath isn't exist"); + return "TargetPath isn't exist!"; + } + } + + static boolean fileIsExist() + { + File file = new File(TargetPath); + return file.exists() || file.mkdirs(); + } + + /** + * 高版本Android保存图片方法 + * + * @param name 图片名 + * @param bm 等待保存的Bitmap + * @return 是否保存成功 + */ + private String saveImageNew(String name, Bitmap bm) + { + ContentValues contentValues = new ContentValues(); + contentValues.put("_display_name", name + format.format(new Date())); + contentValues.put("description", name); + contentValues.put("mime_type", "image/jpeg"); + contentValues.put("relative_path", "DCIM/Tess"); + try (OutputStream outputStream = mContext.getContentResolver() + .openOutputStream(mContext.getContentResolver() + .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues))) + { + bm.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); + Log.d("Save Bitmap", "Save success!"); + return "保存完毕!"; + } + catch (Exception e) + { + e.printStackTrace(); + Log.d("Save Bitmap", "Save fail!"); + return "Exception!"; + } + } + + /** + * 通过URL获取图片 + * + * @param uri - + * @return Bitmap + */ + public Bitmap showImage(Uri uri) + { + try (ParcelFileDescriptor parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(uri, "r")) + { + return BitmapFactory.decodeFileDescriptor(parcelFileDescriptor.getFileDescriptor()); + } + catch (IOException e) + { + e.printStackTrace(); + return null; + } + } + + /** + * 通过图片名获取Tess文件夹对应的图片 + * + * @param imageName 图片名 + * @return Bitmap + */ + public static Bitmap getImages(String imageName) + { + String path = TargetPath + imageName; + return new File(path).exists() ? BitmapFactory.decodeFile(path) : null; + } + + /** + * 通过绝对路径获取Bitmap + * + * @param realPath 绝对路径 + * @return Bitmap + */ + public static Bitmap getRealPathImages(String realPath) + { + return new File(realPath).exists() ? BitmapFactory.decodeFile(realPath) : null; + } + + /** + * 灰度化图像 + * + * @param inputBitmap 需要灰度化图像的Bitmap + * @return 灰度化后的Bitmap + */ + public static Bitmap GrayscaleImage(Bitmap inputBitmap) + { + if (inputBitmap == null) return null; + Mat mat = new Mat(); + Utils.bitmapToMat(inputBitmap, mat); + return GrayscaleImage(mat); + } + + /** + * 灰度化图像 + * + * @param colorImage 需要灰度化图像的Bitmap + * @return 灰度化后的Bitmap + */ + public static Bitmap GrayscaleImage(Mat colorImage) + { + if (colorImage == null) return null; + // 创建一个空的灰度图像 + Mat grayscaleImage = new Mat(); + // 调用cvtColor函数,将彩色图像转换为灰度图像 + Imgproc.cvtColor(colorImage, grayscaleImage, Imgproc.COLOR_RGB2GRAY); + Bitmap result = Bitmap.createBitmap(colorImage.width(), colorImage.height(), Bitmap.Config.ARGB_8888); + Utils.matToBitmap(grayscaleImage, result); + return result; + } + + /** + * 灰度化图像并保存 + */ + private void GrayscaleImage() + { + // 定义源文件夹和目标文件夹的路径 + String sourcePath = Environment.getExternalStorageDirectory() + "/" + Environment.DIRECTORY_DCIM + "/srcImg/"; + String targetPath = Environment.getExternalStorageDirectory() + "/" + Environment.DIRECTORY_DCIM + "/dstImg/"; + // 创建File对象,表示源文件夹和目标文件夹 + File sourceFolder = new File(sourcePath); + File targetFolder = new File(targetPath); + // 检查源文件夹是否存在,如果不存在,打印错误信息并退出 + if (!sourceFolder.exists()) + { + System.out.println("Source folder does not exist."); + return; + } + // 检查目标文件夹是否存在,如果不存在,就创建一个 + if (!targetFolder.exists()) if (!targetFolder.mkdir()) return; + // 获取源文件夹中的所有文件,存放在一个File数组中 + File[] files = sourceFolder.listFiles(); + if (files == null) return; + // 遍历File数组,对每个文件进行灰度化处理 + for (File file : files) + { + // 获取文件 + Bitmap bitmap = BitmapProcess.getRealPathImages(sourcePath + file.getName()); + if (bitmap == null) continue; + // 加载原始图像 + Mat colorImage = new Mat(); + Utils.bitmapToMat(bitmap, colorImage); + // 创建一个空的灰度图像 + Mat grayscaleImage = new Mat(); + // 调用cvtColor函数,将彩色图像转换为灰度图像 + Imgproc.cvtColor(colorImage, grayscaleImage, Imgproc.COLOR_RGB2GRAY); + Utils.matToBitmap(grayscaleImage, bitmap); + // 获取文件的名称,不包括扩展名 + StringBuilder fileName = new StringBuilder(); + for (int j = 0; j < file.getName().split("\\.").length - 1; j++) + { + fileName.append(file.getName().split("\\.")[j]).append("."); + } + Imgcodecs.imwrite(file.getName(), grayscaleImage); + if (file.getName().split("\\.")[file.getName().split("\\.").length - 1].equals("jpg")) + { + try + { + FileOutputStream saveImgOut = new FileOutputStream(new File(targetPath, fileName + "jpg")); + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, saveImgOut); + saveImgOut.flush(); + Log.d("Save Bitmap", "The picture is save to your phone!"); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + else + { + try + { + FileOutputStream saveImgOut = new FileOutputStream(new File(targetPath, fileName + "png")); + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, saveImgOut); + saveImgOut.flush(); + Log.d("Save Bitmap", "The picture is save to your phone!"); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uns/maincar/open_source/utils/ColorHSV.java b/app/src/main/java/com/uns/maincar/open_source/utils/ColorHSV.java new file mode 100644 index 0000000..58395b1 --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/utils/ColorHSV.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.utils; + +/** + *

Hsv色彩空间范围

+ *

以下排序使用HSV的H(色彩度数)排序

+ *

数组下标为2,4,6时是最小值,下标为1,3,5时是最大值

+ *

由于HSV模型的特性,红色被划分为了两个部分,因而使用了两个数组

+ */ +@SuppressWarnings("ALL") +public final class ColorHSV +{ + /* 图形识别色彩数据 */ + //所有H(色彩度) + public static final int[] allHSV = new int[]{0, 180, 0, 255, 150, 255, 110}; + //红色:0-20 + public static final int[] redDownHSV = new int[]{0, 25, 0, 255, 110, 255, 110}; + public static final int[] redDownHSV1 = new int[]{0, 25, 0, 255, 150, 255, 110}; + //红色:160-180 + public static final int[] redUpHSV = new int[]{0, 180, 160, 255, 150, 255, 110}; + public static final int[] redUpHSV1 = new int[]{0, 180, 150, 255, 110, 255, 110}; + //黄色 + public static final int[] yellowHSV = new int[]{0, 43, 25, 255, 150, 255, 110}; + public static final int[] yellowHSV1 = new int[]{0, 43, 25, 255, 110, 255, 225}; + //绿色 + public static final int[] greenHSV = new int[]{0, 70, 55, 255, 150, 255, 110}; + public static final int[] greenHSV1 = new int[]{0, 70, 40, 255, 150, 255, 110}; + //青色 + public static final int[] cyanHSV = new int[]{0, 95, 85, 255, 150, 255, 110}; + public static final int[] cyanHSV1 = new int[]{0, 95, 85, 255, 190, 255, 110}; + //蓝色 + public static final int[] blueHSV = new int[]{0, 120, 110, 255, 150, 255, 110}; + public static final int[] blueHSV1 = new int[]{0, 130, 95, 255, 225, 255, 110}; + public static final int[] blueHSV2 = new int[]{0, 120, 85, 255, 210, 255, 195}; + public static final int[] blueHSV3 = new int[]{0, 120, 85, 255, 210, 255, 110}; + public static final int[] blueHSV4 = new int[]{0, 130, 110, 255, 210, 255, 195}; + //蓝色 - 红色取反 + public static final int[] red2blueHSV = new int[]{0, 140, 110, 255, 150, 255, 110}; + //紫色 + public static final int[] purpleHSV = new int[]{0, 160, 125, 255, 150, 255, 110}; + public static final int[] purpleHSV1 = new int[]{0, 155, 125, 255, 150, 255, 110}; + public static final int[] purpleHSV2 = new int[]{0, 155, 125, 255, 125, 255, 110}; + public static final int[] purpleHSV3 = new int[]{0, 155, 125, 255, 125, 255, 200}; + //实验性 - 白 + public static final int[] whiteHSV = new int[]{0, 110, 0, 60, 0, 255, 225}; + //实验性 - 黑 + public static final int[] blackHSV = new int[]{0, 100, 47, 225, 50, 60, 0}; + + /* 车牌识别数据 */ + //浅蓝0、//黄色1、//品红2、//浅红色3、//蓝色4、//青色5、// 深红色6、//黑色7 车牌蓝底9 车牌绿底10 + public static double[][] PlateDetector_HSV_VALUE_LOW = { + {10, 163, 147}, //浅蓝0 + {77, 163, 147}, //黄色1 + {146, 212, 140},//品红2 + {126, 155, 160},//浅红色3 + {0, 204, 178}, //蓝色4 + {35, 163, 147}, //青色5 + {110, 155, 160},//深红色6 + {0, 0, 0}, //黑色7 + {0, 0, 192}, //标准蓝8 + {0, 190, 190}, //车牌蓝底9 暗的TFT:0,190,190 亮的:0,180,190 + {22, 195, 158}, //车牌绿底10 暗的TFT H:21 S要调高一点:210 V:211 亮的TFT S值要调底一点:110 10,100,148 + {65, 0, 200}, //新能源车牌白变绿渐变 + }; + + public static double[][] PlateDetector_HSV_VALUE_HIGH = { + {47, 255, 255}, //浅蓝0 + {111, 255, 255}, //黄色1 + {241, 255, 255.0}, //品红2 + {150, 255, 255}, //浅红色3 + {21, 255, 255}, //蓝色4 + {75, 255.0, 255}, //青色5 + {150, 255, 255}, //深红色6 + {180, 255, 120}, //黑色7 + {45, 238, 255}, //标准蓝8 + {28, 255, 255}, //车牌蓝底9 亮暗一样 + {73, 255, 255}, //车牌绿底10 暗H:66 亮H:83 + {110, 255, 255}, //新能源车牌白变绿渐变 + }; + + //浅蓝0、//黄色1、//品红2、//浅红色3、//蓝色4、//青色5、// 深红色6、//黑色7 + //暗 S、V=214,211 亮 S、V=176,160 + //浅蓝0、//黄色1、//品红2、//浅红色3、//蓝色4、//青色5、// 深红色6、//黑色7 车牌蓝底9 车牌绿底10 + public static double[][] HSV_VALUE_LOW = { + {13, 176, 160},//浅蓝0 12,214,211 + {67, 176, 160},//黄色1 + {130, 176, 160},//品红2 暗:100, 176,160 亮:130,176,160 + {126, 176, 160},//浅红色3 + {0, 176, 160},//蓝色4 + {30, 176, 160},//青色5 35 + {103, 176, 160},// 深红色6 + {0, 0, 0},//黑色7 暗:0,187,0 亮:0,0,0 + {0, 0, 192},//标准蓝8 + {0, 150, 190},//车牌蓝底9 暗的TFT:0,190,190 亮的:0,180,190 + {22, 104, 161},//车牌绿底10 暗的TFT H:21 S要调高一点:210 V:211 亮的TFT S值要调底一点:110 10,100,148 + }; + + public static double[][] HSV_VALUE_HIGH = { + {30, 255, 255},//浅蓝0 + {111, 255, 255},//黄色1 + {241, 255, 255.0},//品红2 + {150, 255, 255},//浅红色3 + {12, 255, 255},//蓝色4 + {70, 255.0, 255},//青色5 90 + {150, 255, 255},// 深红色6 + {255, 255, 150},//黑色7 暗:28,255,184 亮:255,255,150 + {45, 238, 255},//标准蓝8 + {126, 255, 255},//车牌蓝底9 亮暗一样 + {120, 255, 255},//车牌绿底10 暗H:66 亮H:83 + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/uns/maincar/open_source/utils/ShapeStatistics.java b/app/src/main/java/com/uns/maincar/open_source/utils/ShapeStatistics.java new file mode 100644 index 0000000..81e92f2 --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/utils/ShapeStatistics.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.utils; + +import java.util.HashMap; + +/** + * 形状统计 + */ +public class ShapeStatistics +{ + + //统计形状数量 + private HashMap shapeStatistics = new HashMap<>(); + + /** + * 获取指定形状的数量 + * + * @param shapeName 三角形/矩形/菱形/五角星/圆形/总计 + * @return 数量 + */ + public Integer getCounts(String shapeName) + { + return shapeStatistics.get(shapeName); + } + + /** + * 设置形状的数量 + * + * @param shapeStatistics 包含该形状数量的HashMap对象 + */ + public void setShapeStatistics(HashMap shapeStatistics) + { + this.shapeStatistics = shapeStatistics; + } +} diff --git a/app/src/main/java/com/uns/maincar/open_source/vehicle/YoloV5_tfLite_VIDDetector.java b/app/src/main/java/com/uns/maincar/open_source/vehicle/YoloV5_tfLite_VIDDetector.java new file mode 100644 index 0000000..3794bd2 --- /dev/null +++ b/app/src/main/java/com/uns/maincar/open_source/vehicle/YoloV5_tfLite_VIDDetector.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) https://github.com/gh-xiao/EmbeddedCar + * This file is pull from GitHub open source project + * Integrated by UnknownObject at 2023 - 6 - 7 + */ + +package com.uns.maincar.open_source.vehicle; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.SystemClock; +import android.util.Log; + +import com.google.gson.Gson; + +import org.tensorflow.lite.examples.detection.env.Logger; +import org.tensorflow.lite.examples.detection.tflite.Classifier; +import org.tensorflow.lite.examples.detection.tflite.DetectorFactory; +import org.tensorflow.lite.examples.detection.tflite.YoloV5Classifier; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * 使用基于YoloV5-tfLite模型的交通标志物识别 + */ +public class YoloV5_tfLite_VIDDetector +{ + + // Which detection model to use: by default uses Tensorflow Object Detection API frozen + // checkpoints. +// enum DetectorMode {TF_OD_API} + + //日志对象 + private static final Logger LOGGER = new Logger(); + //枚举常量 - 检测模式 +// private static final DetectorMode MODE = DetectorMode.TF_OD_API; + public static final float MINIMUM_CONFIDENCE_TF_OD_API = 0.3f; + //最小置信度 + public static float minimumConfidence; + //核心检测对象 + private YoloV5Classifier detector; + //模型列表 + private final String[] models = new String[]{"VIDyolov5s-fp16.tflite", "VIDyolov5s-fp16-2.tflite"}; + //检测图片 + private Bitmap SaveBitmap; + private long timestamp = 0; + + public Bitmap getSaveBitmap() + { + return SaveBitmap; + } + + /** + * 加载模型配置 + * + * @param device 使用何种设备加载模型 + * @param numThreads 使用多少线程加载 + * @param assetManager assetManager管理对象 + */ + public boolean LoadModel(String device, int numThreads, AssetManager assetManager) + { + //模型文件 + String modelString = models[0]; + //检测类别(标签) + String labelFilename = "VIDclass.txt"; + /* 线程数(不推荐超过9线程数) */ + if (numThreads > 9) numThreads = 4; + LOGGER.i("Changing model to ***" + modelString + "*** device ***" + device + "***"); + + /* Try to load model. */ + /* 尝试加载模型 */ + try + { + detector = DetectorFactory.getDetector(assetManager, modelString, labelFilename); + // Customize the interpreter to the type of device we want to use. + } + catch (IOException e) + { + e.printStackTrace(); + LOGGER.e(e, "Exception in updateActiveModel()"); +// Toast toast = Toast.makeText(FirstActivity.getContext(), "Classifier could not be initialized", Toast.LENGTH_SHORT); + return false; + } + + switch (device) + { + case "GPU": + detector.useGpu(); + break; + case "NNAPI": + detector.useNNAPI(); + break; + default: + detector.useCPU(); + break; + } + /* 设置线程数 */ + detector.setNumThreads(numThreads); + return true; + } + + /** + * 检测图片 + * + * @param inputBitmap - + * @return 检测结果 + */ + public String processImage(Bitmap inputBitmap) + { + /* 结果列表对象 */ + List recognitions = new LinkedList<>(); + /* 将结果转换成Gson */ + Gson gson = new Gson(); + + if (inputBitmap == null) return gson.toJson(recognitions); + //416*416 + int cropSize = detector.getInputSize(); + System.out.println(cropSize); + + int width = inputBitmap.getWidth(); + int height = inputBitmap.getHeight(); + float scaleWidth = ((float) cropSize) / width; + float scaleHeight = ((float) cropSize) / height; + //矩阵 + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleHeight); + + /* 将输入图片通过矩阵变换得到416*416大小的新图片 */ + Bitmap croppedBitmap = Bitmap.createBitmap(inputBitmap, 0, 0, width, height, matrix, true); + Bitmap draw = croppedBitmap.copy(Bitmap.Config.ARGB_8888, true); + + ++timestamp; + final long currTimestamp = timestamp; + + LOGGER.i("Preparing image " + currTimestamp + " for detection in bg thread."); + /* 利用分类器classifier对图片进行预测分析,得到图片为每个分类的概率. 比较耗时 */ + LOGGER.i("Running detection on image " + currTimestamp); + + final long startTime = SystemClock.uptimeMillis(); + /* 核心检测 */ + final List results = detector.recognizeImage(croppedBitmap); + /* 计算检测时间 */ + long lastProcessingTimeMs = SystemClock.uptimeMillis() - startTime; + /* 检测出多少对象 */ + Log.e("CHECK", "run: " + results.size()); + /* 检测时间 */ + Log.i("Time Spent: ", lastProcessingTimeMs + "ms"); + + /* 筛选通过最低置信度阈值的识别结果 */ + final List mappedRecognitions = new LinkedList<>(); + for (final Classifier.Recognition result : results) + { + final RectF location = result.getLocation(); + if (location != null && result.getConfidence() >= minimumConfidence) + { + result.setLocation(location); + /* 将通过最低置信度的结果添加到新List */ + mappedRecognitions.add(result); + //识别结果 + Log.e("result: ", result.getTitle() + result.getConfidence()); + drawBitmap(result, draw); + } + } + + return gson.toJson(mappedRecognitions.size() > 0 ? mappedRecognitions : recognitions); +// //最终结果 +// if (mappedRecognitions.size() != 0) { +// /* 排列出最高置信度的结果 */ +// Collections.sort(mappedRecognitions, (o1, o2) -> (int) (o1.getConfidence() - o2.getConfidence())); +// Log.e("SUCCESS", String.valueOf(mappedRecognitions.get(0).getConfidence())); +// return mappedRecognitions.get(0).getTitle(); +// } else { +// Log.e("ERROR", "识别错误"); +// return "car"; +// } + } + + private void drawBitmap(Classifier.Recognition result, Bitmap resultBitmap) + { + final Canvas canvas = new Canvas(resultBitmap); + final Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(2.0f); + canvas.drawRect(result.getLocation(), paint); + SaveBitmap = resultBitmap.copy(Bitmap.Config.ARGB_8888, true); + } +} diff --git a/app/src/main/res/layout/activity_single_function_test.xml b/app/src/main/res/layout/activity_single_function_test.xml index 4a8c911..6366afe 100644 --- a/app/src/main/res/layout/activity_single_function_test.xml +++ b/app/src/main/res/layout/activity_single_function_test.xml @@ -185,4 +185,34 @@ android:layout_weight="1" android:text="立即崩溃" /> + + + +