diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt index eac008d..20711a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt @@ -218,17 +218,20 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.GREEN + borderPaint.color = Color.GREEN + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), (it.position[0] + textRectWidth).toInt(), (it.position[1] - textRectHeight).toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - backgroundPaint.color = Color.RED - } else { - backgroundPaint.color = Color.GREEN - } canvas.drawRect(textRect, backgroundPaint) //画文字 @@ -246,11 +249,6 @@ it.position[2].toInt(), it.position[3].toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - borderPaint.color = Color.RED - } else { - borderPaint.color = Color.GREEN - } canvas.drawRect(rect, borderPaint) } diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt index eac008d..20711a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt @@ -218,17 +218,20 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.GREEN + borderPaint.color = Color.GREEN + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), (it.position[0] + textRectWidth).toInt(), (it.position[1] - textRectHeight).toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - backgroundPaint.color = Color.RED - } else { - backgroundPaint.color = Color.GREEN - } canvas.drawRect(textRect, backgroundPaint) //画文字 @@ -246,11 +249,6 @@ it.position[2].toInt(), it.position[3].toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - borderPaint.color = Color.RED - } else { - borderPaint.color = Color.GREEN - } canvas.drawRect(rect, borderPaint) } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt index f9fb342..ffba700 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt @@ -34,11 +34,9 @@ val fontMetrics = textPaint.fontMetrics textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - backgroundPaint.color = Color.BLUE backgroundPaint.style = Paint.Style.FILL backgroundPaint.isAntiAlias = true - borderPaint.color = Color.BLUE borderPaint.style = Paint.Style.STROKE borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 borderPaint.isAntiAlias = true @@ -68,6 +66,14 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.BLUE + borderPaint.color = Color.BLUE + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt index eac008d..20711a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt @@ -218,17 +218,20 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.GREEN + borderPaint.color = Color.GREEN + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), (it.position[0] + textRectWidth).toInt(), (it.position[1] - textRectHeight).toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - backgroundPaint.color = Color.RED - } else { - backgroundPaint.color = Color.GREEN - } canvas.drawRect(textRect, backgroundPaint) //画文字 @@ -246,11 +249,6 @@ it.position[2].toInt(), it.position[3].toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - borderPaint.color = Color.RED - } else { - borderPaint.color = Color.GREEN - } canvas.drawRect(rect, borderPaint) } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt index f9fb342..ffba700 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt @@ -34,11 +34,9 @@ val fontMetrics = textPaint.fontMetrics textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - backgroundPaint.color = Color.BLUE backgroundPaint.style = Paint.Style.FILL backgroundPaint.isAntiAlias = true - borderPaint.color = Color.BLUE borderPaint.style = Paint.Style.STROKE borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 borderPaint.isAntiAlias = true @@ -68,6 +66,14 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.BLUE + borderPaint.color = Color.BLUE + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), diff --git a/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml new file mode 100644 index 0000000..58f001a --- /dev/null +++ b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt index eac008d..20711a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt @@ -218,17 +218,20 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.GREEN + borderPaint.color = Color.GREEN + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), (it.position[0] + textRectWidth).toInt(), (it.position[1] - textRectHeight).toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - backgroundPaint.color = Color.RED - } else { - backgroundPaint.color = Color.GREEN - } canvas.drawRect(textRect, backgroundPaint) //画文字 @@ -246,11 +249,6 @@ it.position[2].toInt(), it.position[3].toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - borderPaint.color = Color.RED - } else { - borderPaint.color = Color.GREEN - } canvas.drawRect(rect, borderPaint) } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt index f9fb342..ffba700 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt @@ -34,11 +34,9 @@ val fontMetrics = textPaint.fontMetrics textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - backgroundPaint.color = Color.BLUE backgroundPaint.style = Paint.Style.FILL backgroundPaint.isAntiAlias = true - borderPaint.color = Color.BLUE borderPaint.style = Paint.Style.STROKE borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 borderPaint.isAntiAlias = true @@ -68,6 +66,14 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.BLUE + borderPaint.color = Color.BLUE + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), diff --git a/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml new file mode 100644 index 0000000..58f001a --- /dev/null +++ b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml b/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml new file mode 100644 index 0000000..1f7f273 --- /dev/null +++ b/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/cpp/yolov8ncnn.cpp b/app/src/main/cpp/yolov8ncnn.cpp index d537de5..bba3daf 100644 --- a/app/src/main/cpp/yolov8ncnn.cpp +++ b/app/src/main/cpp/yolov8ncnn.cpp @@ -392,8 +392,8 @@ } JNIEXPORT jboolean JNICALL -Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz) { - g_yolo->j_state = 3; +Java_com_casic_br_ar_app_external_Yolov8ncnn_onRestart(JNIEnv *env, jobject thiz, jint state) { + g_yolo->j_state = state; return JNI_TRUE; } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt index 4333a39..7efe658 100644 --- a/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt +++ b/app/src/main/java/com/casic/br/ar/app/external/Yolov8ncnn.kt @@ -51,5 +51,5 @@ external fun onPause(): Boolean - external fun onRestart(): Boolean + external fun onRestart(state: Int): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java new file mode 100644 index 0000000..133e17e --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/model/HiddenTroubleResult.java @@ -0,0 +1,24 @@ +package com.casic.br.ar.app.model; + +import com.casic.br.ar.app.external.YoloResult; + +public class HiddenTroubleResult extends YoloResult { + private String alarmCode; + private String warning; + + public String getAlarmCode() { + return alarmCode; + } + + public void setAlarmCode(String alarmCode) { + this.alarmCode = alarmCode; + } + + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt index d278f53..b4d468f 100644 --- a/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ar/app/utils/LocaleConstant.kt @@ -52,14 +52,14 @@ "安全帽", "安全标识", "安全绳", - "对讲机", + "对讲设备", "尖头水枪", "工服", "开关", "报警装置", "接头", "施工路牌", - "气体检测仪", + "气体检测报警仪", "水带", "水带_矩形", "流量计", diff --git a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt index fb6fa58..8cf56a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt +++ b/app/src/main/java/com/casic/br/ar/app/view/CheckModeActivity.kt @@ -16,12 +16,12 @@ import com.casic.br.ar.app.extensions.convert2YoloResult import com.casic.br.ar.app.extensions.getSceneByTarget import com.casic.br.ar.app.extensions.getSceneCodeByName -import com.casic.br.ar.app.extensions.isContains import com.casic.br.ar.app.extensions.isInScene import com.casic.br.ar.app.external.INativeCallback import com.casic.br.ar.app.external.YoloResult import com.casic.br.ar.app.external.Yolov8ncnn import com.casic.br.ar.app.model.DictionaryModel +import com.casic.br.ar.app.model.HiddenTroubleResult import com.casic.br.ar.app.model.SceneCheckManifestModel import com.casic.br.ar.app.utils.LocaleConstant import com.casic.br.ar.app.utils.RuntimeCache @@ -33,7 +33,6 @@ import com.casic.br.ar.app.vm.InspectionViewModel import com.casic.br.ar.app.widgets.AddHiddenTroubleDialog import com.casic.br.ar.app.widgets.CheckItemDialog -import com.casic.br.ar.app.widgets.DetectResultDialog import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show @@ -59,11 +58,9 @@ private val context = this private val targetSet by lazy { HashSet() } private val detectedSceneSet by lazy { HashSet() } - private val detectedTargetSet by lazy { HashSet() } private val yolov8ncnn by lazy { Yolov8ncnn() } private val mat by lazy { Mat() } private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } - private val detectResultDialog by lazy { DetectResultDialog(this) } private val checkItemDialog by lazy { CheckItemDialog(this) } private val addHiddenTroubleDialog by lazy { AddHiddenTroubleDialog(this) } private lateinit var inspectionViewModel: InspectionViewModel @@ -80,7 +77,6 @@ private var checkItemCount = 0 private var checkPassCount = 0 private var troubleCount = 0 - private var isConfirmedByUser = false override fun initOnCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -131,7 +127,11 @@ alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java] imageFileViewModel.resultModel.observe(this) { if (it.code == 200) { + //添加此次检查发现的隐患 alarmViewModel.uploadDetectTargetAlarm(context, alarmCode, it.data.toString()) + troubleCount++ + binding.troubleCountView.text = "${troubleCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" } } @@ -148,48 +148,39 @@ } hiddenTroubleViewModel = ViewModelProvider(this)[HiddenTroubleViewModel::class.java] - hiddenTroubleViewModel.addTroubleResult.observe(this) { - if (it.code == 200) { - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - isConfirmedByUser = true - } - } //队列处理清单内容 timer = Timer() timer.schedule(object : TimerTask() { override fun run() { - if (isConfirmedByUser && checkItemLinkedList.isNotEmpty()) { - lifecycleScope.launch(Dispatchers.Main) { - if (!detectResultDialog.isShowing) { - isConfirmedByUser = false - //取出队列头部元素 - headItem = checkItemLinkedList.poll()!! - checkItemDialog.updateCheckItemView(headItem.checkItem, - object : CheckItemDialog.OnDialogButtonClickListener { - override fun onCheckPassClick() { - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = - "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - isConfirmedByUser = true + runOnUiThread { + if (!checkItemDialog.isShowing && checkItemLinkedList.isNotEmpty() && !addHiddenTroubleDialog.isShowing) { + //取出队列头部元素 + headItem = checkItemLinkedList.poll()!! + checkItemDialog.updateCheckItemView(headItem.checkItem, + object : CheckItemDialog.OnDialogButtonClickListener { + override fun onCheckPassClick() { + checkPassCount++ + binding.checkedCountView.text = "${checkPassCount}项" + binding.noCheckCountView.text = "${checkItemLinkedList.size}项" + if (checkItemLinkedList.isEmpty()) { + stopCheck() } + } - override fun onHaveTroubleClick() { - //弹新的框 + override fun onHaveTroubleClick(file: File?) { + if (file == null) { showAddHiddenTroubleDialog() + } else { + imageFileViewModel.uploadImage(file) } - }).show() - } + } + } + ).show() } } } - }, 1000, 5000) + }, 2000, 5000) } private fun showAddHiddenTroubleDialog() { @@ -220,7 +211,6 @@ override fun onCancelClick() { //将取消的那一项重新添加进队列 checkItemLinkedList.offer(headItem) - isConfirmedByUser = true } }).show() } @@ -327,6 +317,17 @@ } binding.detectView.updateTargetPosition(segmentationResults, detectResults) + //筛选隐患获取alarmCode + runOnUiThread { + if (checkItemDialog.isShowing) { + checkItemDialog.updateTargetResults(segmentationResults, detectResults, mat, + object : CheckItemDialog.OnDetectCallback { + override fun onTroubleResult(result: HiddenTroubleResult) { + alarmCode = result.alarmCode + } + }) + } + } lifecycleScope.launch(Dispatchers.Main) { detectResults.forEach { @@ -336,180 +337,6 @@ } } } - - //只显示隐患 - var warnings = "" - when (RuntimeCache.sceneName) { - "配电箱" -> { - if (!detectResults.isContains(16)) { - warnings = "配电箱无警示标识" - alarmCode = "DistributionBoxHasNoWarningSign" - } - - if (!detectResults.isContains(36)) { - warnings = "配电箱内无电路图" - alarmCode = "DistributionBoxHasNoCircuitDiagram" - } - - if (detectResults.isContains(35)) { - warnings = "配电箱内部电线裸露" - alarmCode = "" - } - - if (detectResults.isContains(41)) { - warnings = "配电箱有跨电线" - alarmCode = "DistributionBoxHasNoJumperWire" - } - - if (segmentationResults.isNotEmpty()) { - warnings = "配电箱内电线杂乱" - alarmCode = "DistributionBoxHasTangledWire" - } - } - - "有限空间作业" -> { - if (detectResults.isContains(3)) { - if (!detectResults.isContains(15)) { - warnings = "未佩戴安全帽" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyHat" - } - - if (!detectResults.isContains(20)) { - warnings = "未着工服" - alarmCode = "ConfinedSpaceHasNoWorkerClothes" - } - - if (!detectResults.isContains(15) && !detectResults.isContains(17)) { - warnings = "没有佩戴安全带、安全绳" - alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" - } - } - - if (!detectResults.isContains(9)) { - warnings = "未发现呼吸防护设备" - alarmCode = "ConfinedSpaceHasNoWorkerMask" - } - - if (!detectResults.isContains(42) && !detectResults.isContains(37)) { - warnings = "未发现路锥、警戒线" - alarmCode = "ConfinedSpaceHasNoEnclosure" - } - - if (!detectResults.isContains(14)) { - warnings = "未发现安全告知牌" - alarmCode = "ConfinedSpaceHasNoWarningSign" - } - - if (!detectResults.isContains(51)) { - warnings = "未发现通风设备" - alarmCode = "ConfinedSpaceHasNoAirSupply" - } - - if (!detectResults.isContains(33)) { - warnings = "未发现井下照明设备" - alarmCode = "ConfinedSpaceHasNoLighting" - } - - if (!detectResults.isContains(18)) { - warnings = "未发现井下对讲设备" - alarmCode = "ConfinedSpaceHasNoIntercom" - } - - if (!detectResults.isContains(0)) { - warnings = "未发现施工三脚架" - alarmCode = "ConfinedSpaceHasNoTripod" - } - - if (!detectResults.isContains(25)) { - warnings = "未发现气体检测仪" - alarmCode = "ConfinedSpaceHasNoGasDetector" - } - } - - "非居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - - "居民用户" -> { - if (segmentationResults.isContains(4)) { - warnings = "腐蚀、锈蚀" - alarmCode = "NonResidentUserHasPipelineRust" - } - - if (segmentationResults.isContains(0)) { - warnings = "软管不可恢复的弯折拉伸" - alarmCode = "NonResidentUserHasHoseFlexure" - } - - if (detectResults.isContains(49)) { - warnings = "非专用软管" - alarmCode = "NonResidentUserHasNonDedicatedHose" - } - - if (detectResults.isContains(1)) { - warnings = "软管有接头或三通" - alarmCode = "NonResidentUserHasNoHoseJoint" - } - - if (!detectResults.isContains(4)) { - warnings = "未发现切断阀" - alarmCode = "NonResidentUserHasNoShutoffValve" - } - - if (!detectResults.isContains(34)) { - warnings = "未发现熄火保护装置" - alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" - } - - if (!detectResults.isContains(22)) { - warnings = "未发现燃气泄漏报警装置" - alarmCode = "NonResidentUserHasNoAlarmDevice" - } - } - } - - if (warnings == "") { - return - } - - //显示弹框 - val message = weakReferenceHandler.obtainMessage() - message.what = 2024082902 - message.obj = warnings - weakReferenceHandler.sendMessage(message) } override fun onDetect(output: ArrayList) { @@ -542,85 +369,41 @@ } override fun handleMessage(msg: Message): Boolean { - when (msg.what) { - 2024082901 -> { - val scene = msg.obj as String - AlertControlDialog.Builder() - .setContext(this) - .setTitle("温馨提示") - .setMessage("识别到${scene}场景,是否开始检查?") - .setNegativeButton("重新识别") - .setPositiveButton("开始检查") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - //暂停算法 - yolov8ncnn.onPause() + if (msg.what == 2024082901) { + val scene = msg.obj as String + AlertControlDialog.Builder() + .setContext(this) + .setTitle("温馨提示") + .setMessage("识别到${scene}场景,是否开始检查?") + .setNegativeButton("重新识别") + .setPositiveButton("开始检查") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + //暂停算法 + yolov8ncnn.onPause() - RuntimeCache.sceneName = scene - detectedSceneSet.add(scene) - binding.sceneNameView.text = scene + RuntimeCache.sceneName = scene + detectedSceneSet.add(scene) + binding.sceneNameView.text = scene - //获取检查清单数目 - for (dic in RuntimeCache.sceneDicModels) { - if (dic.name == scene) { - checkManifestViewModel.getCheckManifestByScene( - context, dic.value - ) + //调用多模型 + yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) + isDetectTarget = true - isDetectTarget = true - //调用多模型 - yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 1), false) - - //识别完场景,显示第一条规则 - isConfirmedByUser = true - return - } + //获取检查清单数目 + for (dic in RuntimeCache.sceneDicModels) { + if (dic.name == scene) { + checkManifestViewModel.getCheckManifestByScene(context, dic.value) + break } } + } - override fun onCancelClick() { - yolov8ncnn.onRestart() - } - }).build().show() - } - - 2024082902 -> { - //弹框 - if (detectResultDialog.isShowing) { - return true - } - - val target = msg.obj as String - if (detectedTargetSet.contains(target)) { - "该隐患已识别过".show(this) - return true - } - - if (mat.width() > 0 || mat.height() > 0) { - detectResultDialog.updateDialogContentView( - target, mat, object : DetectResultDialog.OnDialogButtonClickListener { - override fun onCheckPassClick(file: File) { - detectedTargetSet.add(target) - checkPassCount++ - binding.checkedCountView.text = "${checkPassCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - if (checkItemLinkedList.isEmpty()) { - stopCheck() - } - } - - override fun onHaveTroubleClick(file: File) { - imageFileViewModel.uploadImage(file) - detectedTargetSet.add(target) - - troubleCount++ - binding.troubleCountView.text = "${troubleCount}项" - binding.noCheckCountView.text = "${checkItemLinkedList.size}项" - } - }).show() - } - } + override fun onCancelClick() { + yolov8ncnn.onRestart(3) + } + }).build().show() } return true } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt index c383773..9589042 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/CheckItemDialog.kt @@ -2,16 +2,44 @@ import android.app.Dialog import android.content.Context +import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import android.util.Log +import com.casic.br.ar.app.R +import com.casic.br.ar.app.callback.OnImageCompressListener import com.casic.br.ar.app.databinding.DialogCheckItemBinding +import com.casic.br.ar.app.extensions.compressImage +import com.casic.br.ar.app.extensions.isContains +import com.casic.br.ar.app.external.YoloResult +import com.casic.br.ar.app.model.HiddenTroubleResult +import com.casic.br.ar.app.utils.LocaleConstant import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File +import java.util.Stack +import java.util.Timer +import java.util.TimerTask -class CheckItemDialog(context: Context) : Dialog(context) { +class CheckItemDialog(context: Context) : Dialog(context), Handler.Callback { private val kTag = "CheckItemDialog" private val binding: DialogCheckItemBinding by binding() - private lateinit var item: String + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private val detectedTargetSet by lazy { HashSet() } + private val timer by lazy { Timer() } + private var checkResultStack = Stack>() + private lateinit var checkItem: String + private lateinit var mat: Mat private lateinit var listener: OnDialogButtonClickListener override fun onCreate(savedInstanceState: Bundle?) { @@ -24,20 +52,357 @@ } binding.haveTroubleButton.setOnClickListener { - listener.onHaveTroubleClick() + listener.onHaveTroubleClick(null) dismiss() } } - fun updateCheckItemView(item: String, listener: OnDialogButtonClickListener): CheckItemDialog { - this.item = item + private fun mat2Image(): String? { + if (mat.width() > 0 || mat.height() > 0) { + //mat转图片 + val bitmap = Bitmap.createBitmap( + mat.width(), mat.height(), Bitmap.Config.ARGB_8888 + ) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + return imagePath + } + return null + } + + fun updateCheckItemView( + checkItem: String, listener: OnDialogButtonClickListener + ): CheckItemDialog { + this.checkItem = checkItem this.listener = listener - binding.messageView.text = item + binding.messageView.text = checkItem + binding.checkPassButton.text = "检查通过" + binding.haveTroubleButton.text = "存在隐患" + + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25) + + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25) + + //每1s查一次缓存数据 + timer.schedule(object : TimerTask() { + override fun run() { + weakReferenceHandler.sendEmptyMessage(2024092403) + } + }, 0, 1000) return this } + fun updateTargetResults( + segmentationResults: MutableList, detectResults: MutableList, + mat: Mat, callback: OnDetectCallback + ) { + this.mat = mat + 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.onTroubleResult(result) + break + } else { + //配电箱内。有开关说明在内部 + if (detectResults.isContains(21)) { + if (!detectResults.isContains(36)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoCircuitDiagram" + result.warning = "配电箱内无电路图" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(35)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = "配电箱内部电线裸露" + callback.onTroubleResult(result) + break + } else if (detectResults.isContains(41)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "DistributionBoxHasNoJumperWire" + result.warning = "配电箱有跨电线" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(17)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWorkerSafelyLine" + result.warning = "没有佩戴安全带、安全绳" + callback.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(22)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoStoveFlameoutProtection" + result.warning = "未发现燃气泄漏报警装置" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(4)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasNoShutoffValve" + result.warning = "未发现切断阀" + callback.onTroubleResult(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.onTroubleResult(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.onTroubleResult(result) + break + } else if (!detectResults.isContains(42) || !detectResults.isContains(37)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoEnclosure" + result.warning = "未发现路锥、警戒线" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(14)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoWarningSign" + result.warning = "未发现安全告知牌" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(50) || !detectResults.isContains(51)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoAirSupply" + result.warning = "未发现通风设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(33)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoLighting" + result.warning = "未发现井下照明设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(18)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoIntercom" + result.warning = "未发现井下对讲设备" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(0)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoTripod" + result.warning = "未发现施工三脚架" + callback.onTroubleResult(result) + break + } else if (!detectResults.isContains(25)) { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "ConfinedSpaceHasNoGasDetector" + result.warning = "未发现气体检测仪" + callback.onTroubleResult(result) + break + } + } + } + + for (it in segmentationResults) { + when (it.type) { + 4 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasPipelineRust" + result.warning = "腐蚀、锈蚀" + callback.onTroubleResult(result) + break + } + + 0 -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "NonResidentUserHasHoseFlexure" + result.warning = "软管不可恢复的弯折拉伸" + callback.onTroubleResult(result) + break + } + + else -> { + val result = HiddenTroubleResult() + result.position = it.position + result.alarmCode = "" + result.warning = LocaleConstant.SEGMENTATION_ARRAY[it.type] + break + } + } + } + + checkResultStack.push(detectResults) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + 2024092401 -> { + binding.checkPassButton.text = "检查通过(${msg.obj}s)" + binding.checkPassButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.checkPassButton.setBackgroundResource(R.drawable.button_dialog_selector_purple_25_solid) + } + + 2024092402 -> { + binding.haveTroubleButton.text = "存在隐患(${msg.obj}s)" + binding.haveTroubleButton.typeface = Typeface.defaultFromStyle(Typeface.BOLD) + binding.haveTroubleButton.setBackgroundResource(R.drawable.button_dialog_selector_red_25_solid) + } + + 2024092403 -> { + if (checkResultStack.isEmpty()) { + return true + } + val detectResults = checkResultStack.pop() + //隐患筛选 + if (checkItem.contains("安全帽")) { + if (detectResults.isContains(13) && !detectResults.isContains(15)) { + //未佩戴安全帽 + startCountDownTimer("未佩戴安全帽", true) + return true + } + } + + if (checkItem.contains("防护服") || checkItem.contains("工服")) { + if (detectResults.isContains(13) && !detectResults.isContains(20)) { + //未着工服 + startCountDownTimer("未着工服", true) + return true + } + } + + if (checkItem.contains("安全带") || checkItem.contains("安全绳")) { + if (detectResults.isContains(13) && !detectResults.isContains(17)) { + startCountDownTimer("没有佩戴安全带、安全绳", true) + return true + } + } + + for (result in detectResults) { + val typeName = LocaleConstant.CLASS_NAMES_ARRAY[result.type] + if (checkItem.contains(typeName) || checkItem.contains("对讲设备")) { + startCountDownTimer(typeName, false) + return true + } + } + } + } + return true + } + + private var isCountDownCompleted = true + private fun startCountDownTimer(typeName: String, saveImage: Boolean) { + if (detectedTargetSet.contains(typeName)) { + Log.d(kTag, detectedTargetSet.toJson()) + return + } + if (isCountDownCompleted) { + isCountDownCompleted = false + object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + val countDownTime = millisUntilFinished / 1000 + val message = weakReferenceHandler.obtainMessage() + message.obj = countDownTime + //显示倒计时 + if (saveImage) { + message.what = 2024092402 + weakReferenceHandler.sendMessage(message) + } else { + message.what = 2024092401 + weakReferenceHandler.sendMessage(message) + } + } + + override fun onFinish() { + if (saveImage) { + val imagePath = mat2Image() + if (imagePath == null) { + listener.onHaveTroubleClick(null) + } else { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + listener.onHaveTroubleClick(file) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + detectedTargetSet.add(typeName) + listener.onCheckPassClick() + isCountDownCompleted = true + dismiss() + } + }.start() + } + } + interface OnDialogButtonClickListener { fun onCheckPassClick() - fun onHaveTroubleClick() + fun onHaveTroubleClick(file: File?) + } + + interface OnDetectCallback { + fun onTroubleResult(result: HiddenTroubleResult) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt deleted file mode 100644 index 942e682..0000000 --- a/app/src/main/java/com/casic/br/ar/app/widgets/DetectResultDialog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.casic.br.ar.app.widgets - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import android.os.Bundle -import com.casic.br.ar.app.callback.OnImageCompressListener -import com.casic.br.ar.app.databinding.DialogDetectResultBinding -import com.casic.br.ar.app.extensions.compressImage -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.createImageFileDir -import com.pengxh.kt.lite.extensions.initDialogLayoutParams -import com.pengxh.kt.lite.extensions.saveImage -import org.opencv.android.Utils -import org.opencv.core.Mat -import java.io.File - -class DetectResultDialog(context: Context) : Dialog(context) { - - private val kTag = "DetectResultDialog" - private val binding: DialogDetectResultBinding by binding() - private lateinit var target: String - private lateinit var mat: Mat - private lateinit var listener: OnDialogButtonClickListener - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initDialogLayoutParams(0.8f) - - binding.checkPassButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onCheckPassClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - - binding.haveTroubleButton.setOnClickListener { - //mat转图片 - val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(mat, bitmap, true) - val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" - bitmap.saveImage(imagePath) - - //压缩 - imagePath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - listener.onHaveTroubleClick(file) - dismiss() - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - fun updateDialogContentView( - target: String, mat: Mat, listener: OnDialogButtonClickListener - ): DetectResultDialog { - this.target = target - this.mat = mat - this.listener = listener - - binding.messageView.text = target - return this - } - - interface OnDialogButtonClickListener { - fun onCheckPassClick(file: File) - fun onHaveTroubleClick(file: File) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt new file mode 100644 index 0000000..d1d6090 --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTargetDialog.kt @@ -0,0 +1,99 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTargetBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTargetDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTargetDialog" + private val binding: DialogDetectTargetBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var mat: Mat + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + val imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + //压缩 + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTargetDialog { + this.target = target + this.mat = mat + this.listener = listener + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + listener.onCheckPassClick() + dismiss() + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "检查通过(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt new file mode 100644 index 0000000..9cec5cc --- /dev/null +++ b/app/src/main/java/com/casic/br/ar/app/widgets/DetectTroubleDialog.kt @@ -0,0 +1,106 @@ +package com.casic.br.ar.app.widgets + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Message +import com.casic.br.ar.app.callback.OnImageCompressListener +import com.casic.br.ar.app.databinding.DialogDetectTroubleBinding +import com.casic.br.ar.app.extensions.compressImage +import com.pengxh.kt.lite.extensions.binding +import com.pengxh.kt.lite.extensions.createImageFileDir +import com.pengxh.kt.lite.extensions.initDialogLayoutParams +import com.pengxh.kt.lite.extensions.saveImage +import com.pengxh.kt.lite.utils.WeakReferenceHandler +import org.opencv.android.Utils +import org.opencv.core.Mat +import java.io.File + +class DetectTroubleDialog(context: Context) : Dialog(context), Handler.Callback { + + private val kTag = "DetectTroubleDialog" + private val binding: DialogDetectTroubleBinding by binding() + private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } + private lateinit var target: String + private lateinit var imagePath: String + private lateinit var listener: OnDialogButtonClickListener + private lateinit var countDownTimer: CountDownTimer + private var countDownCount = 3 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initDialogLayoutParams(0.8f) + + binding.checkPassButton.setOnClickListener { + countDownTimer.cancel() + listener.onCheckPassClick() + dismiss() + } + + binding.haveTroubleButton.setOnClickListener { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + } + + fun updateDialogContentView( + target: String, mat: Mat, listener: OnDialogButtonClickListener + ): DetectTroubleDialog { + this.target = target + this.listener = listener + + //mat转图片 + val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888) + Utils.matToBitmap(mat, bitmap, true) + imagePath = "${context.createImageFileDir()}/${System.currentTimeMillis()}.png" + bitmap.saveImage(imagePath) + + binding.messageView.text = target + countDownTimer = object : CountDownTimer(3000, 1000) { + override fun onTick(millisUntilFinished: Long) { + //显示倒计时 + weakReferenceHandler.sendEmptyMessage(2024092401) + } + + override fun onFinish() { + imagePath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + countDownTimer.cancel() + listener.onHaveTroubleClick(file) + dismiss() + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + }.start() + return this + } + + override fun handleMessage(msg: Message): Boolean { + if (msg.what == 2024092401) { + binding.checkPassButton.text = "存在隐患(${countDownCount}s)" + countDownCount-- + } + return true + } + + interface OnDialogButtonClickListener { + fun onCheckPassClick() + fun onHaveTroubleClick(file: File) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt index eac008d..20711a1 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/FreeModeYoloDetectView.kt @@ -218,17 +218,20 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.GREEN + borderPaint.color = Color.GREEN + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), (it.position[0] + textRectWidth).toInt(), (it.position[1] - textRectHeight).toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - backgroundPaint.color = Color.RED - } else { - backgroundPaint.color = Color.GREEN - } canvas.drawRect(textRect, backgroundPaint) //画文字 @@ -246,11 +249,6 @@ it.position[2].toInt(), it.position[3].toInt() ) - if (label == "非专用软管" || label == "电线暴露") { - borderPaint.color = Color.RED - } else { - borderPaint.color = Color.GREEN - } canvas.drawRect(rect, borderPaint) } diff --git a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt index f9fb342..ffba700 100644 --- a/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt +++ b/app/src/main/java/com/casic/br/ar/app/widgets/YoloTargetDetectView.kt @@ -34,11 +34,9 @@ val fontMetrics = textPaint.fontMetrics textHeight = (fontMetrics.bottom - fontMetrics.top).toInt() - backgroundPaint.color = Color.BLUE backgroundPaint.style = Paint.Style.FILL backgroundPaint.isAntiAlias = true - borderPaint.color = Color.BLUE borderPaint.style = Paint.Style.STROKE borderPaint.strokeWidth = 2f.dp2px(context) //设置线宽 borderPaint.isAntiAlias = true @@ -68,6 +66,14 @@ val textRectWidth = textLength * 1.1 //文字背景宽度 val textRectHeight = textHeight * 1.1 //文字背景高度 + if (it.type == 35 || it.type == 41 || it.type == 47 || it.type == 49) { + backgroundPaint.color = Color.RED + borderPaint.color = Color.RED + } else { + backgroundPaint.color = Color.BLUE + borderPaint.color = Color.BLUE + } + textRect.set( it.position[0].toInt(), it.position[1].toInt(), diff --git a/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml new file mode 100644 index 0000000..58f001a --- /dev/null +++ b/app/src/main/res/drawable/button_dialog_selector_purple_25_solid.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml b/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml new file mode 100644 index 0000000..1f7f273 --- /dev/null +++ b/app/src/main/res/drawable/button_dialog_selector_red_25_solid.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_detect_result.xml b/app/src/main/res/layout/dialog_detect_result.xml deleted file mode 100644 index 6061467..0000000 --- a/app/src/main/res/layout/dialog_detect_result.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - -