diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt index 30b82a4..0be917e 100644 --- a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt +++ b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt @@ -4,10 +4,10 @@ import android.content.Context import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import com.casic.br.app.callback.OnImageCompressListener import com.casic.br.app.databinding.DialogDetectResultBinding import com.casic.br.app.extensions.compressImage +import com.casic.br.app.model.HiddenTroubleResult import com.pengxh.kt.lite.extensions.binding import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams @@ -20,7 +20,7 @@ private val kTag = "DetectResultDialog" private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String + private var alarmCodeArray = ArrayList() private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener @@ -38,8 +38,7 @@ //压缩 imagePath.compressImage(context, object : OnImageCompressListener { override fun onSuccess(file: File) { - Log.d(kTag, "absolutePath: ${file.absolutePath}") - listener.onConfirmClick(file) + listener.onConfirmClick(file, alarmCodeArray) dismiss() } @@ -53,16 +52,32 @@ } fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener + results: ArrayList, troubles: Set, mat: Mat, + listener: OnDialogButtonClickListener ): DetectResultDialog { - this.target = target this.mat = mat this.listener = listener - binding.messageView.text = target + + //序列化alarmCode + val alarmCodeArray = ArrayList() + results.forEach { + alarmCodeArray.add(it.alarmCode) + } + this.alarmCodeArray = alarmCodeArray + + val string = StringBuffer() + troubles.forEachIndexed { index, s -> + if (index == troubles.size - 1) { + string.append(s) + } else { + string.append(s).append(",") + } + } + binding.messageView.text = string return this } interface OnDialogButtonClickListener { - fun onConfirmClick(file: File) + fun onConfirmClick(file: File, alarmCodeArray: ArrayList) } } \ No newline at end of file diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt index 30b82a4..0be917e 100644 --- a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt +++ b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt @@ -4,10 +4,10 @@ import android.content.Context import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import com.casic.br.app.callback.OnImageCompressListener import com.casic.br.app.databinding.DialogDetectResultBinding import com.casic.br.app.extensions.compressImage +import com.casic.br.app.model.HiddenTroubleResult import com.pengxh.kt.lite.extensions.binding import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams @@ -20,7 +20,7 @@ private val kTag = "DetectResultDialog" private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String + private var alarmCodeArray = ArrayList() private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener @@ -38,8 +38,7 @@ //压缩 imagePath.compressImage(context, object : OnImageCompressListener { override fun onSuccess(file: File) { - Log.d(kTag, "absolutePath: ${file.absolutePath}") - listener.onConfirmClick(file) + listener.onConfirmClick(file, alarmCodeArray) dismiss() } @@ -53,16 +52,32 @@ } fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener + results: ArrayList, troubles: Set, mat: Mat, + listener: OnDialogButtonClickListener ): DetectResultDialog { - this.target = target this.mat = mat this.listener = listener - binding.messageView.text = target + + //序列化alarmCode + val alarmCodeArray = ArrayList() + results.forEach { + alarmCodeArray.add(it.alarmCode) + } + this.alarmCodeArray = alarmCodeArray + + val string = StringBuffer() + troubles.forEachIndexed { index, s -> + if (index == troubles.size - 1) { + string.append(s) + } else { + string.append(s).append(",") + } + } + binding.messageView.text = string return this } interface OnDialogButtonClickListener { - fun onConfirmClick(file: File) + fun onConfirmClick(file: File, alarmCodeArray: ArrayList) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt deleted file mode 100644 index 324e199..0000000 --- a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.casic.br.app.widgets - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.text.TextPaint -import android.util.AttributeSet -import android.view.View -import com.casic.br.app.extensions.isContains -import com.casic.br.app.external.YoloResult -import com.casic.br.app.model.HiddenTroubleResult -import com.casic.br.app.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.sp2px - -class YoloTargetDetectView constructor(context: Context, attrs: AttributeSet) : - View(context, attrs) { - - private val kTag = "DetectView" - private val textPaint by lazy { TextPaint() } - private val backgroundPaint by lazy { Paint() } - private val borderPaint by lazy { Paint() } - private val textRect by lazy { Rect() } - private val rect by lazy { Rect() } - private var hiddenTroubles: MutableList = ArrayList() - private var textHeight = 0 - - init { - textPaint.color = Color.WHITE - textPaint.isAntiAlias = true - textPaint.textAlign = Paint.Align.CENTER - textPaint.textSize = 16f.sp2px(context) - val fontMetrics = textPaint.fontMetrics - textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - - backgroundPaint.style = Paint.Style.FILL - backgroundPaint.isAntiAlias = true - - borderPaint.style = Paint.Style.STROKE - borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 - borderPaint.isAntiAlias = true - } - - fun updateTargetPosition( - segmentationResults: MutableList, detectResults: MutableList, - callback: OnDetectCallback - ) { - //筛选出隐患 - val hiddenTroubles = ArrayList() - for (it in detectResults) { - if (it.type == 44) { - //it此时代表配电箱 - if (!detectResults.isContains(16)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoWarningSign" - result.warning = "配电箱无警示标识" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else { - //配电箱内。有开关说明在内部 - if (detectResults.isContains(21)) { - if (!detectResults.isContains(36)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoCircuitDiagram" - result.warning = "配电箱内无电路图" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(35)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "配电箱内部电线裸露" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(41)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoJumperWire" - result.warning = "配电箱有跨电线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - } else if (it.type == 13) { - //it此时代表头 - if (!detectResults.isContains(15)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - result.warning = "未佩戴安全帽" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 3) { - //it此时代表人 - if (!detectResults.isContains(20)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" - result.warning = "未着工服" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(17)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - result.warning = "没有佩戴安全带、安全绳" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 31 || it.type == 32) { - //it此时代表灶台、灶眼 - if (!detectResults.isContains(34)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "未发现熄火保护装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(22)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - result.warning = "未发现燃气泄漏报警装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(4)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoShutoffValve" - result.warning = "未发现切断阀" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 2 || it.type == 49) { - //it此时代表软管 - if (detectResults.isContains(1)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoHoseJoint" - result.warning = "软管有接头或三通" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 9 || it.type == 42 || it.type == 37 || it.type == 14 || it.type == 50 || it.type == 51 || it.type == 33 || it.type == 18 || it.type == 0 || it.type == 25) { - if (!detectResults.isContains(9)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerMask" - result.warning = "未发现呼吸防护设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoEnclosure" - result.warning = "未发现路锥、警戒线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(14)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWarningSign" - result.warning = "未发现安全告知牌" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoAirSupply" - result.warning = "未发现通风设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(33)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoLighting" - result.warning = "未发现井下照明设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(18)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoIntercom" - result.warning = "未发现井下对讲设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(0)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoTripod" - result.warning = "未发现施工三脚架" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(25)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoGasDetector" - result.warning = "未发现气体检测仪" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - - for (it in segmentationResults) { - when (it.type) { - 4 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasPipelineRust" - result.warning = "腐蚀、锈蚀" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - 0 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasHoseFlexure" - result.warning = "软管不可恢复的弯折拉伸" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - else -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] - hiddenTroubles.add(result) - } - } - } - - this.hiddenTroubles = hiddenTroubles - - postInvalidate() - } - - //TODO 去掉隐患显示,换一种方式展现 -// override fun onDraw(canvas: Canvas) { -// super.onDraw(canvas) -// hiddenTroubles.forEach { -// drawAlarmTips(canvas, it, it.warning) -// } -// } - - private fun drawAlarmTips(canvas: Canvas, it: YoloResult, label: String) { - val textLength = textPaint.measureText(label) - val textRectWidth = textLength * 1.1 //文字背景宽度 - val textRectHeight = textHeight * 1.1 //文字背景高度 - - textRect.set( - it.position[0].toInt(), - it.position[1].toInt(), - (it.position[0] + textRectWidth).toInt(), - (it.position[1] - textRectHeight).toInt() - ) - backgroundPaint.color = Color.RED - canvas.drawRect(textRect, backgroundPaint) - - //画文字 - canvas.drawText( - label, - (it.position[0] + textRectWidth / 2).toFloat(), - (it.position[1] - textRectHeight / 4).toFloat(), - textPaint - ) - - //画框 - rect.set( - it.position[0].toInt(), - it.position[1].toInt(), - it.position[2].toInt(), - it.position[3].toInt() - ) - borderPaint.color = Color.RED - canvas.drawRect(rect, borderPaint) - } - - interface OnDetectCallback { - fun onDetected(result: HiddenTroubleResult) - } -} \ No newline at end of file diff --git a/app/src/main/cpp/yolo.cpp b/app/src/main/cpp/yolo.cpp index 10f6444..1fff513 100644 --- a/app/src/main/cpp/yolo.cpp +++ b/app/src/main/cpp/yolo.cpp @@ -844,7 +844,7 @@ if (obj.label == 35 || obj.label == 41 || obj.label == 47 || obj.label == 49) { rect_cc = cv::Scalar(255, 0, 0); } else { - rect_cc = cv::Scalar(0, 255, 0); + rect_cc = cv::Scalar(0, 0, 255); } cv::rectangle(rgb, obj.rect, rect_cc, 2); diff --git a/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt new file mode 100644 index 0000000..1ce858b --- /dev/null +++ b/app/src/main/java/com/casic/br/app/callback/OnYoloResultSiftCallback.kt @@ -0,0 +1,7 @@ +package com.casic.br.app.callback + +import com.casic.br.app.model.HiddenTroubleResult + +interface OnYoloResultSiftCallback { + fun onResultSifted(results: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt index 30b82a4..0be917e 100644 --- a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt +++ b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt @@ -4,10 +4,10 @@ import android.content.Context import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import com.casic.br.app.callback.OnImageCompressListener import com.casic.br.app.databinding.DialogDetectResultBinding import com.casic.br.app.extensions.compressImage +import com.casic.br.app.model.HiddenTroubleResult import com.pengxh.kt.lite.extensions.binding import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams @@ -20,7 +20,7 @@ private val kTag = "DetectResultDialog" private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String + private var alarmCodeArray = ArrayList() private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener @@ -38,8 +38,7 @@ //压缩 imagePath.compressImage(context, object : OnImageCompressListener { override fun onSuccess(file: File) { - Log.d(kTag, "absolutePath: ${file.absolutePath}") - listener.onConfirmClick(file) + listener.onConfirmClick(file, alarmCodeArray) dismiss() } @@ -53,16 +52,32 @@ } fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener + results: ArrayList, troubles: Set, mat: Mat, + listener: OnDialogButtonClickListener ): DetectResultDialog { - this.target = target this.mat = mat this.listener = listener - binding.messageView.text = target + + //序列化alarmCode + val alarmCodeArray = ArrayList() + results.forEach { + alarmCodeArray.add(it.alarmCode) + } + this.alarmCodeArray = alarmCodeArray + + val string = StringBuffer() + troubles.forEachIndexed { index, s -> + if (index == troubles.size - 1) { + string.append(s) + } else { + string.append(s).append(",") + } + } + binding.messageView.text = string return this } interface OnDialogButtonClickListener { - fun onConfirmClick(file: File) + fun onConfirmClick(file: File, alarmCodeArray: ArrayList) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt deleted file mode 100644 index 324e199..0000000 --- a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.casic.br.app.widgets - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.text.TextPaint -import android.util.AttributeSet -import android.view.View -import com.casic.br.app.extensions.isContains -import com.casic.br.app.external.YoloResult -import com.casic.br.app.model.HiddenTroubleResult -import com.casic.br.app.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.sp2px - -class YoloTargetDetectView constructor(context: Context, attrs: AttributeSet) : - View(context, attrs) { - - private val kTag = "DetectView" - private val textPaint by lazy { TextPaint() } - private val backgroundPaint by lazy { Paint() } - private val borderPaint by lazy { Paint() } - private val textRect by lazy { Rect() } - private val rect by lazy { Rect() } - private var hiddenTroubles: MutableList = ArrayList() - private var textHeight = 0 - - init { - textPaint.color = Color.WHITE - textPaint.isAntiAlias = true - textPaint.textAlign = Paint.Align.CENTER - textPaint.textSize = 16f.sp2px(context) - val fontMetrics = textPaint.fontMetrics - textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - - backgroundPaint.style = Paint.Style.FILL - backgroundPaint.isAntiAlias = true - - borderPaint.style = Paint.Style.STROKE - borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 - borderPaint.isAntiAlias = true - } - - fun updateTargetPosition( - segmentationResults: MutableList, detectResults: MutableList, - callback: OnDetectCallback - ) { - //筛选出隐患 - val hiddenTroubles = ArrayList() - for (it in detectResults) { - if (it.type == 44) { - //it此时代表配电箱 - if (!detectResults.isContains(16)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoWarningSign" - result.warning = "配电箱无警示标识" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else { - //配电箱内。有开关说明在内部 - if (detectResults.isContains(21)) { - if (!detectResults.isContains(36)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoCircuitDiagram" - result.warning = "配电箱内无电路图" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(35)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "配电箱内部电线裸露" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(41)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoJumperWire" - result.warning = "配电箱有跨电线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - } else if (it.type == 13) { - //it此时代表头 - if (!detectResults.isContains(15)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - result.warning = "未佩戴安全帽" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 3) { - //it此时代表人 - if (!detectResults.isContains(20)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" - result.warning = "未着工服" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(17)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - result.warning = "没有佩戴安全带、安全绳" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 31 || it.type == 32) { - //it此时代表灶台、灶眼 - if (!detectResults.isContains(34)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "未发现熄火保护装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(22)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - result.warning = "未发现燃气泄漏报警装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(4)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoShutoffValve" - result.warning = "未发现切断阀" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 2 || it.type == 49) { - //it此时代表软管 - if (detectResults.isContains(1)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoHoseJoint" - result.warning = "软管有接头或三通" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 9 || it.type == 42 || it.type == 37 || it.type == 14 || it.type == 50 || it.type == 51 || it.type == 33 || it.type == 18 || it.type == 0 || it.type == 25) { - if (!detectResults.isContains(9)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerMask" - result.warning = "未发现呼吸防护设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoEnclosure" - result.warning = "未发现路锥、警戒线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(14)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWarningSign" - result.warning = "未发现安全告知牌" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoAirSupply" - result.warning = "未发现通风设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(33)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoLighting" - result.warning = "未发现井下照明设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(18)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoIntercom" - result.warning = "未发现井下对讲设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(0)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoTripod" - result.warning = "未发现施工三脚架" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(25)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoGasDetector" - result.warning = "未发现气体检测仪" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - - for (it in segmentationResults) { - when (it.type) { - 4 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasPipelineRust" - result.warning = "腐蚀、锈蚀" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - 0 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasHoseFlexure" - result.warning = "软管不可恢复的弯折拉伸" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - else -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] - hiddenTroubles.add(result) - } - } - } - - this.hiddenTroubles = hiddenTroubles - - postInvalidate() - } - - //TODO 去掉隐患显示,换一种方式展现 -// override fun onDraw(canvas: Canvas) { -// super.onDraw(canvas) -// hiddenTroubles.forEach { -// drawAlarmTips(canvas, it, it.warning) -// } -// } - - private fun drawAlarmTips(canvas: Canvas, it: YoloResult, label: String) { - val textLength = textPaint.measureText(label) - val textRectWidth = textLength * 1.1 //文字背景宽度 - val textRectHeight = textHeight * 1.1 //文字背景高度 - - textRect.set( - it.position[0].toInt(), - it.position[1].toInt(), - (it.position[0] + textRectWidth).toInt(), - (it.position[1] - textRectHeight).toInt() - ) - backgroundPaint.color = Color.RED - canvas.drawRect(textRect, backgroundPaint) - - //画文字 - canvas.drawText( - label, - (it.position[0] + textRectWidth / 2).toFloat(), - (it.position[1] - textRectHeight / 4).toFloat(), - textPaint - ) - - //画框 - rect.set( - it.position[0].toInt(), - it.position[1].toInt(), - it.position[2].toInt(), - it.position[3].toInt() - ) - borderPaint.color = Color.RED - canvas.drawRect(rect, borderPaint) - } - - interface OnDetectCallback { - fun onDetected(result: HiddenTroubleResult) - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_check_result.xml b/app/src/main/res/layout/activity_check_result.xml index 479e34a..beb1879 100644 --- a/app/src/main/res/layout/activity_check_result.xml +++ b/app/src/main/res/layout/activity_check_result.xml @@ -31,7 +31,8 @@ ) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt index 30b82a4..0be917e 100644 --- a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt +++ b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt @@ -4,10 +4,10 @@ import android.content.Context import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import com.casic.br.app.callback.OnImageCompressListener import com.casic.br.app.databinding.DialogDetectResultBinding import com.casic.br.app.extensions.compressImage +import com.casic.br.app.model.HiddenTroubleResult import com.pengxh.kt.lite.extensions.binding import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams @@ -20,7 +20,7 @@ private val kTag = "DetectResultDialog" private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String + private var alarmCodeArray = ArrayList() private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener @@ -38,8 +38,7 @@ //压缩 imagePath.compressImage(context, object : OnImageCompressListener { override fun onSuccess(file: File) { - Log.d(kTag, "absolutePath: ${file.absolutePath}") - listener.onConfirmClick(file) + listener.onConfirmClick(file, alarmCodeArray) dismiss() } @@ -53,16 +52,32 @@ } fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener + results: ArrayList, troubles: Set, mat: Mat, + listener: OnDialogButtonClickListener ): DetectResultDialog { - this.target = target this.mat = mat this.listener = listener - binding.messageView.text = target + + //序列化alarmCode + val alarmCodeArray = ArrayList() + results.forEach { + alarmCodeArray.add(it.alarmCode) + } + this.alarmCodeArray = alarmCodeArray + + val string = StringBuffer() + troubles.forEachIndexed { index, s -> + if (index == troubles.size - 1) { + string.append(s) + } else { + string.append(s).append(",") + } + } + binding.messageView.text = string return this } interface OnDialogButtonClickListener { - fun onConfirmClick(file: File) + fun onConfirmClick(file: File, alarmCodeArray: ArrayList) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt deleted file mode 100644 index 324e199..0000000 --- a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.casic.br.app.widgets - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.text.TextPaint -import android.util.AttributeSet -import android.view.View -import com.casic.br.app.extensions.isContains -import com.casic.br.app.external.YoloResult -import com.casic.br.app.model.HiddenTroubleResult -import com.casic.br.app.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.sp2px - -class YoloTargetDetectView constructor(context: Context, attrs: AttributeSet) : - View(context, attrs) { - - private val kTag = "DetectView" - private val textPaint by lazy { TextPaint() } - private val backgroundPaint by lazy { Paint() } - private val borderPaint by lazy { Paint() } - private val textRect by lazy { Rect() } - private val rect by lazy { Rect() } - private var hiddenTroubles: MutableList = ArrayList() - private var textHeight = 0 - - init { - textPaint.color = Color.WHITE - textPaint.isAntiAlias = true - textPaint.textAlign = Paint.Align.CENTER - textPaint.textSize = 16f.sp2px(context) - val fontMetrics = textPaint.fontMetrics - textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - - backgroundPaint.style = Paint.Style.FILL - backgroundPaint.isAntiAlias = true - - borderPaint.style = Paint.Style.STROKE - borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 - borderPaint.isAntiAlias = true - } - - fun updateTargetPosition( - segmentationResults: MutableList, detectResults: MutableList, - callback: OnDetectCallback - ) { - //筛选出隐患 - val hiddenTroubles = ArrayList() - for (it in detectResults) { - if (it.type == 44) { - //it此时代表配电箱 - if (!detectResults.isContains(16)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoWarningSign" - result.warning = "配电箱无警示标识" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else { - //配电箱内。有开关说明在内部 - if (detectResults.isContains(21)) { - if (!detectResults.isContains(36)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoCircuitDiagram" - result.warning = "配电箱内无电路图" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(35)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "配电箱内部电线裸露" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(41)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoJumperWire" - result.warning = "配电箱有跨电线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - } else if (it.type == 13) { - //it此时代表头 - if (!detectResults.isContains(15)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - result.warning = "未佩戴安全帽" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 3) { - //it此时代表人 - if (!detectResults.isContains(20)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" - result.warning = "未着工服" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(17)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - result.warning = "没有佩戴安全带、安全绳" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 31 || it.type == 32) { - //it此时代表灶台、灶眼 - if (!detectResults.isContains(34)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "未发现熄火保护装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(22)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - result.warning = "未发现燃气泄漏报警装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(4)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoShutoffValve" - result.warning = "未发现切断阀" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 2 || it.type == 49) { - //it此时代表软管 - if (detectResults.isContains(1)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoHoseJoint" - result.warning = "软管有接头或三通" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 9 || it.type == 42 || it.type == 37 || it.type == 14 || it.type == 50 || it.type == 51 || it.type == 33 || it.type == 18 || it.type == 0 || it.type == 25) { - if (!detectResults.isContains(9)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerMask" - result.warning = "未发现呼吸防护设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoEnclosure" - result.warning = "未发现路锥、警戒线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(14)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWarningSign" - result.warning = "未发现安全告知牌" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoAirSupply" - result.warning = "未发现通风设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(33)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoLighting" - result.warning = "未发现井下照明设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(18)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoIntercom" - result.warning = "未发现井下对讲设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(0)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoTripod" - result.warning = "未发现施工三脚架" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(25)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoGasDetector" - result.warning = "未发现气体检测仪" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - - for (it in segmentationResults) { - when (it.type) { - 4 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasPipelineRust" - result.warning = "腐蚀、锈蚀" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - 0 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasHoseFlexure" - result.warning = "软管不可恢复的弯折拉伸" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - else -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] - hiddenTroubles.add(result) - } - } - } - - this.hiddenTroubles = hiddenTroubles - - postInvalidate() - } - - //TODO 去掉隐患显示,换一种方式展现 -// override fun onDraw(canvas: Canvas) { -// super.onDraw(canvas) -// hiddenTroubles.forEach { -// drawAlarmTips(canvas, it, it.warning) -// } -// } - - private fun drawAlarmTips(canvas: Canvas, it: YoloResult, label: String) { - val textLength = textPaint.measureText(label) - val textRectWidth = textLength * 1.1 //文字背景宽度 - val textRectHeight = textHeight * 1.1 //文字背景高度 - - textRect.set( - it.position[0].toInt(), - it.position[1].toInt(), - (it.position[0] + textRectWidth).toInt(), - (it.position[1] - textRectHeight).toInt() - ) - backgroundPaint.color = Color.RED - canvas.drawRect(textRect, backgroundPaint) - - //画文字 - canvas.drawText( - label, - (it.position[0] + textRectWidth / 2).toFloat(), - (it.position[1] - textRectHeight / 4).toFloat(), - textPaint - ) - - //画框 - rect.set( - it.position[0].toInt(), - it.position[1].toInt(), - it.position[2].toInt(), - it.position[3].toInt() - ) - borderPaint.color = Color.RED - canvas.drawRect(rect, borderPaint) - } - - interface OnDetectCallback { - fun onDetected(result: HiddenTroubleResult) - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_check_result.xml b/app/src/main/res/layout/activity_check_result.xml index 479e34a..beb1879 100644 --- a/app/src/main/res/layout/activity_check_result.xml +++ b/app/src/main/res/layout/activity_check_result.xml @@ -31,7 +31,8 @@ - - ) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/extensions/Set.kt b/app/src/main/java/com/casic/br/app/extensions/Set.kt new file mode 100644 index 0000000..39ad323 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/extensions/Set.kt @@ -0,0 +1,11 @@ +package com.casic.br.app.extensions + +/** + * Set求交集 + * */ +fun Set.intersect(other: Set): Set = this.filter { it in other }.toSet() + +/** + * Set求差集 + * */ +fun Set.difference(other: Set): Set = this.filterNot { it in other }.toSet() \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt new file mode 100644 index 0000000..2b8c2a7 --- /dev/null +++ b/app/src/main/java/com/casic/br/app/utils/YoloTargetDetectHelper.kt @@ -0,0 +1,205 @@ +package com.casic.br.app.utils + +import com.casic.br.app.callback.OnYoloResultSiftCallback +import com.casic.br.app.extensions.isContains +import com.casic.br.app.external.YoloResult +import com.casic.br.app.model.HiddenTroubleResult + +class YoloTargetDetectHelper(private val callback: OnYoloResultSiftCallback) { + fun siftHiddenTrouble( + segmentationResults: MutableList, detectResults: MutableList + ) { + val hiddenTroubles = ArrayList() + + //结果包含配电箱 + if (detectResults.isContains(44)) { + //且还包括开关 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(35)) { + //包含电线裸露 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + hiddenTroubles.add(result) + } + + if (detectResults.isContains(41)) { + //包含跨电线 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + hiddenTroubles.add(result) + } + } else { + //不包含警示标识 + val result = HiddenTroubleResult() + result.alarmCode = "DistributionBoxHasNoWarningSign" + result.warning = "配电箱无警示标识" + hiddenTroubles.add(result) + } + } + + //结果包含头 + if (detectResults.isContains(13)) { + if (!detectResults.isContains(15)) { + //不包含安全帽 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" + result.warning = "未佩戴安全帽" + hiddenTroubles.add(result) + } + } + + //结果包含人 + if (detectResults.isContains(3)) { + if (!detectResults.isContains(20)) { + //不包含工服 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" + result.warning = "未着工服" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(17)) { + //不包含安全带、安全绳 + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + hiddenTroubles.add(result) + } + } + + //结果包含灶台、灶眼 + if (detectResults.isContains(31) || detectResults.isContains(32)) { + if (!detectResults.isContains(34)) { + //不包含熄火保护装置 + val result = HiddenTroubleResult() + result.alarmCode = "" + result.warning = "未发现熄火保护装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(22)) { + //不包含燃气泄漏报警装置 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + hiddenTroubles.add(result) + } + + if (!detectResults.isContains(4)) { + //不包含切断阀 + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + hiddenTroubles.add(result) + } + } + + //结果包含专用软管、非专用软管 + if (detectResults.isContains(2) || detectResults.isContains(49)) { + //包含接头或三通 + if (detectResults.isContains(1) || detectResults.isContains(23)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasNoHoseJoint" + result.warning = "软管有接头或三通" + hiddenTroubles.add(result) + } + } + + //只有有限空间作业才筛选以下隐患 + if (RuntimeCache.sceneName == "有限空间作业") { + //结果不包含呼吸防护设备 + if (!detectResults.isContains(9)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWorkerMask" + result.warning = "未发现呼吸防护设备" + hiddenTroubles.add(result) + } + + //结果不包含路锥、警戒线 + if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + hiddenTroubles.add(result) + } + + //结果不包含安全告知牌 + if (!detectResults.isContains(14) || + !detectResults.isContains(5) || + !detectResults.isContains(24) + ) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + hiddenTroubles.add(result) + } + + //结果不包含通风设备 + if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + hiddenTroubles.add(result) + } + + //结果不包含井下照明设备 + if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + hiddenTroubles.add(result) + } + + //结果不包含对讲设备 + if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + hiddenTroubles.add(result) + } + + //结果不包含施工三脚架 + if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + hiddenTroubles.add(result) + } + + //结果不包含气体检测仪 + if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + hiddenTroubles.add(result) + } + } + + if (!segmentationResults.isContains(4)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + hiddenTroubles.add(result) + } + + if (!segmentationResults.isContains(0)) { + val result = HiddenTroubleResult() + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + hiddenTroubles.add(result) + } + + //结果统一回调 + callback.onResultSifted(hiddenTroubles) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt index 081d30f..c0e48f5 100644 --- a/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt +++ b/app/src/main/java/com/casic/br/app/view/StartCheckByYoloActivity.kt @@ -14,24 +14,25 @@ import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.casic.br.app.R +import com.casic.br.app.callback.OnYoloResultSiftCallback import com.casic.br.app.databinding.ActivityStartCheckByYoloBinding import com.casic.br.app.extensions.convert2YoloResult +import com.casic.br.app.extensions.difference import com.casic.br.app.extensions.getSceneByTarget import com.casic.br.app.extensions.initImmersionBar import com.casic.br.app.external.INativeCallback import com.casic.br.app.external.YoloResult import com.casic.br.app.external.Yolov8ncnn -import com.casic.br.app.model.DictionaryModel import com.casic.br.app.model.HiddenTroubleResult import com.casic.br.app.utils.LocaleConstant import com.casic.br.app.utils.LocationManager import com.casic.br.app.utils.RuntimeCache +import com.casic.br.app.utils.YoloTargetDetectHelper import com.casic.br.app.vm.AlarmViewModel import com.casic.br.app.vm.ConfigViewModel import com.casic.br.app.vm.ImageFileViewModel import com.casic.br.app.vm.InspectionViewModel import com.casic.br.app.widgets.DetectResultDialog -import com.casic.br.app.widgets.YoloTargetDetectView import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertColor import com.pengxh.kt.lite.extensions.navigatePageTo @@ -46,13 +47,14 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.osgi.OpenCVNativeLoader import java.io.File class StartCheckByYoloActivity : KotlinBaseActivity(), - SurfaceHolder.Callback, INativeCallback, Handler.Callback { + SurfaceHolder.Callback, INativeCallback, Handler.Callback, OnYoloResultSiftCallback { private val kTag = "StartCheckActivity" private val context = this @@ -63,14 +65,14 @@ private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val detectResultDialog by lazy { DetectResultDialog(this) } + private val detectHelper by lazy { YoloTargetDetectHelper(this) } private lateinit var inspectionViewModel: InspectionViewModel private lateinit var configViewModel: ConfigViewModel private lateinit var imageFileViewModel: ImageFileViewModel private lateinit var alarmViewModel: AlarmViewModel private var inspectionAddress = "" - private var mainDicModels: MutableList = ArrayList() private var isDetectTarget = false - private var alarmCode = "" + private var alarmCodeArray = ArrayList() override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -118,7 +120,7 @@ configViewModel.getDictionaryByCode(context, "pitfallBigType") configViewModel.dictionary.observe(this) { if (it.code == 200) { - mainDicModels = it.data + RuntimeCache.mainDicModels = it.data } } @@ -126,7 +128,17 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { - alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + //报警图片提交成功后,分批提交隐患 + lifecycleScope.launch { + flow { + alarmCodeArray.forEach { code -> + emit(code) + delay(1000) + } + }.collect { code -> + alarmViewModel.uploadDetectTargetAlarm(context, code, it.data.toString()) + } + } } } } @@ -149,7 +161,6 @@ binding.addButton.setOnClickListener { RuntimeCache.mat = mat - RuntimeCache.mainDicModels = mainDicModels navigatePageTo() } @@ -259,26 +270,13 @@ detectResults.add(it.convert2YoloResult(this)) } - binding.detectView.updateTargetPosition( - segmentationResults, detectResults, - object : YoloTargetDetectView.OnDetectCallback { - override fun onDetected(result: HiddenTroubleResult) { - //显示隐患弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = result - //延迟显示 - weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) - } - }) + //实时筛选隐患结果 + detectHelper.siftHiddenTrouble(segmentationResults, detectResults) runOnUiThread { detectResults.forEach { val label = LocaleConstant.TARGET_NAMES_ARRAY[it.type] targetSet.add(label) - binding.tipsTagView.visibility = View.VISIBLE - binding.tipsTagView.text = "${targetSet.size}" - if (RuntimeCache.sceneName == "有限空间作业") { when (it.type) { 37 -> RuntimeCache.detectResult.add(0) @@ -291,14 +289,16 @@ 33 -> RuntimeCache.detectResult.add(7) } } else { - when (it.type) { - 2 -> RuntimeCache.detectResult.add(1) - 31, 32 -> RuntimeCache.detectResult.add(0) - 34 -> RuntimeCache.detectResult.add(0) - 22 -> RuntimeCache.detectResult.add(4) - 4 -> RuntimeCache.detectResult.add(3) - } +// when (it.type) { +// 2 -> RuntimeCache.detectResult.add(1) +// 31, 32 -> RuntimeCache.detectResult.add(0) +// 34 -> RuntimeCache.detectResult.add(0) +// 22 -> RuntimeCache.detectResult.add(4) +// 4 -> RuntimeCache.detectResult.add(3) +// } } + binding.tipsTagView.visibility = View.VISIBLE + binding.tipsTagView.text = "${targetSet.size}" } } } @@ -324,6 +324,18 @@ weakReferenceHandler.sendMessage(message) } + override fun onResultSifted(results: ArrayList) { + if (results.isEmpty()) { + return + } + //显示隐患弹框 + val message = weakReferenceHandler.obtainMessage() + message.what = 2024082902 + message.obj = results + //延迟显示 + weakReferenceHandler.sendMessageDelayed(message, 10 * 1000L) + } + override fun handleMessage(msg: Message): Boolean { when (msg.what) { 2024082901 -> { @@ -362,18 +374,32 @@ return true } - val target = msg.obj as HiddenTroubleResult - if (detectedTargetSet.contains(target.warning)) { + //安全转换。转换失败直接return,不影响程序运行 + val results = msg.obj as? ArrayList ?: return true + val troubleSet = HashSet() + results.forEach { + troubleSet.add(it.warning) + } + + val intersectResult = troubleSet.difference(detectedTargetSet) + //若差集为空,则说明隐患已全部识别过 + if (intersectResult.isEmpty()) { return true } + //显示差集的隐患 if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView(target.warning, mat, + detectResultDialog.updateDialogContentView(results, intersectResult, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onConfirmClick(file: File) { - alarmCode = target.alarmCode + override fun onConfirmClick( + file: File, alarmCodeArray: ArrayList + ) { + //一张图里可能会包含多种隐患 + this@StartCheckByYoloActivity.alarmCodeArray = alarmCodeArray imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target.warning) + intersectResult.forEach { + detectedTargetSet.add(it) + } } }).show() } diff --git a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt index 30b82a4..0be917e 100644 --- a/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt +++ b/app/src/main/java/com/casic/br/app/widgets/DetectResultDialog.kt @@ -4,10 +4,10 @@ import android.content.Context import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import com.casic.br.app.callback.OnImageCompressListener import com.casic.br.app.databinding.DialogDetectResultBinding import com.casic.br.app.extensions.compressImage +import com.casic.br.app.model.HiddenTroubleResult import com.pengxh.kt.lite.extensions.binding import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams @@ -20,7 +20,7 @@ private val kTag = "DetectResultDialog" private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String + private var alarmCodeArray = ArrayList() private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener @@ -38,8 +38,7 @@ //压缩 imagePath.compressImage(context, object : OnImageCompressListener { override fun onSuccess(file: File) { - Log.d(kTag, "absolutePath: ${file.absolutePath}") - listener.onConfirmClick(file) + listener.onConfirmClick(file, alarmCodeArray) dismiss() } @@ -53,16 +52,32 @@ } fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener + results: ArrayList, troubles: Set, mat: Mat, + listener: OnDialogButtonClickListener ): DetectResultDialog { - this.target = target this.mat = mat this.listener = listener - binding.messageView.text = target + + //序列化alarmCode + val alarmCodeArray = ArrayList() + results.forEach { + alarmCodeArray.add(it.alarmCode) + } + this.alarmCodeArray = alarmCodeArray + + val string = StringBuffer() + troubles.forEachIndexed { index, s -> + if (index == troubles.size - 1) { + string.append(s) + } else { + string.append(s).append(",") + } + } + binding.messageView.text = string return this } interface OnDialogButtonClickListener { - fun onConfirmClick(file: File) + fun onConfirmClick(file: File, alarmCodeArray: ArrayList) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt deleted file mode 100644 index 324e199..0000000 --- a/app/src/main/java/com/casic/br/app/widgets/YoloTargetDetectView.kt +++ /dev/null @@ -1,309 +0,0 @@ -package com.casic.br.app.widgets - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.text.TextPaint -import android.util.AttributeSet -import android.view.View -import com.casic.br.app.extensions.isContains -import com.casic.br.app.external.YoloResult -import com.casic.br.app.model.HiddenTroubleResult -import com.casic.br.app.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.sp2px - -class YoloTargetDetectView constructor(context: Context, attrs: AttributeSet) : - View(context, attrs) { - - private val kTag = "DetectView" - private val textPaint by lazy { TextPaint() } - private val backgroundPaint by lazy { Paint() } - private val borderPaint by lazy { Paint() } - private val textRect by lazy { Rect() } - private val rect by lazy { Rect() } - private var hiddenTroubles: MutableList = ArrayList() - private var textHeight = 0 - - init { - textPaint.color = Color.WHITE - textPaint.isAntiAlias = true - textPaint.textAlign = Paint.Align.CENTER - textPaint.textSize = 16f.sp2px(context) - val fontMetrics = textPaint.fontMetrics - textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - - backgroundPaint.style = Paint.Style.FILL - backgroundPaint.isAntiAlias = true - - borderPaint.style = Paint.Style.STROKE - borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 - borderPaint.isAntiAlias = true - } - - fun updateTargetPosition( - segmentationResults: MutableList, detectResults: MutableList, - callback: OnDetectCallback - ) { - //筛选出隐患 - val hiddenTroubles = ArrayList() - for (it in detectResults) { - if (it.type == 44) { - //it此时代表配电箱 - if (!detectResults.isContains(16)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoWarningSign" - result.warning = "配电箱无警示标识" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else { - //配电箱内。有开关说明在内部 - if (detectResults.isContains(21)) { - if (!detectResults.isContains(36)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoCircuitDiagram" - result.warning = "配电箱内无电路图" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(35)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "配电箱内部电线裸露" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (detectResults.isContains(41)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "DistributionBoxHasNoJumperWire" - result.warning = "配电箱有跨电线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - } else if (it.type == 13) { - //it此时代表头 - if (!detectResults.isContains(15)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - result.warning = "未佩戴安全帽" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 3) { - //it此时代表人 - if (!detectResults.isContains(20)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerClothes" - result.warning = "未着工服" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(17)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - result.warning = "没有佩戴安全带、安全绳" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 31 || it.type == 32) { - //it此时代表灶台、灶眼 - if (!detectResults.isContains(34)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = "未发现熄火保护装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(22)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - result.warning = "未发现燃气泄漏报警装置" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(4)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoShutoffValve" - result.warning = "未发现切断阀" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 2 || it.type == 49) { - //it此时代表软管 - if (detectResults.isContains(1)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasNoHoseJoint" - result.warning = "软管有接头或三通" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } else if (it.type == 9 || it.type == 42 || it.type == 37 || it.type == 14 || it.type == 50 || it.type == 51 || it.type == 33 || it.type == 18 || it.type == 0 || it.type == 25) { - if (!detectResults.isContains(9)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWorkerMask" - result.warning = "未发现呼吸防护设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoEnclosure" - result.warning = "未发现路锥、警戒线" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(14)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoWarningSign" - result.warning = "未发现安全告知牌" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoAirSupply" - result.warning = "未发现通风设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(33)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoLighting" - result.warning = "未发现井下照明设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(18)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoIntercom" - result.warning = "未发现井下对讲设备" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(0)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoTripod" - result.warning = "未发现施工三脚架" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } else if (!detectResults.isContains(25)) { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "ConfinedSpaceHasNoGasDetector" - result.warning = "未发现气体检测仪" - callback.onDetected(result) - hiddenTroubles.add(result) - break - } - } - } - - for (it in segmentationResults) { - when (it.type) { - 4 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasPipelineRust" - result.warning = "腐蚀、锈蚀" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - 0 -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "NonResidentUserHasHoseFlexure" - result.warning = "软管不可恢复的弯折拉伸" - callback.onDetected(result) - hiddenTroubles.add(result) - } - - else -> { - val result = HiddenTroubleResult() - result.position = it.position - result.alarmCode = "" - result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] - hiddenTroubles.add(result) - } - } - } - - this.hiddenTroubles = hiddenTroubles - - postInvalidate() - } - - //TODO 去掉隐患显示,换一种方式展现 -// override fun onDraw(canvas: Canvas) { -// super.onDraw(canvas) -// hiddenTroubles.forEach { -// drawAlarmTips(canvas, it, it.warning) -// } -// } - - private fun drawAlarmTips(canvas: Canvas, it: YoloResult, label: String) { - val textLength = textPaint.measureText(label) - val textRectWidth = textLength * 1.1 //文字背景宽度 - val textRectHeight = textHeight * 1.1 //文字背景高度 - - textRect.set( - it.position[0].toInt(), - it.position[1].toInt(), - (it.position[0] + textRectWidth).toInt(), - (it.position[1] - textRectHeight).toInt() - ) - backgroundPaint.color = Color.RED - canvas.drawRect(textRect, backgroundPaint) - - //画文字 - canvas.drawText( - label, - (it.position[0] + textRectWidth / 2).toFloat(), - (it.position[1] - textRectHeight / 4).toFloat(), - textPaint - ) - - //画框 - rect.set( - it.position[0].toInt(), - it.position[1].toInt(), - it.position[2].toInt(), - it.position[3].toInt() - ) - borderPaint.color = Color.RED - canvas.drawRect(rect, borderPaint) - } - - interface OnDetectCallback { - fun onDetected(result: HiddenTroubleResult) - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_check_result.xml b/app/src/main/res/layout/activity_check_result.xml index 479e34a..beb1879 100644 --- a/app/src/main/res/layout/activity_check_result.xml +++ b/app/src/main/res/layout/activity_check_result.xml @@ -31,7 +31,8 @@ - -