package com.tievd.jyz.handler.alg.common; import cn.hutool.core.collection.ConcurrentHashSet; import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.tievd.cube.modules.system.entity.SysDepart; import com.tievd.jyz.cache.CarPlaceCache; import com.tievd.jyz.cache.DepartCache; import com.tievd.jyz.cache.DeviceCache; import com.tievd.jyz.cache.EventCodeRelOilCache; import com.tievd.jyz.constants.SystemConstant; import com.tievd.jyz.dto.CarScoreCacheDTO; import com.tievd.jyz.entity.*; import com.tievd.jyz.handler.alg.AlgHandleInterface; import com.tievd.jyz.mapper.*; import com.tievd.jyz.mqtt.command.MqttCommandReceiver; import com.tievd.jyz.mqtt.dto.EventInfoDTO; import com.tievd.jyz.service.ICarInfoService; import com.tievd.jyz.util.StringCampareUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * 车辆识别处理类 * @author timi */ @Slf4j @Component public class CarIdentificationAlgHandler implements AlgHandleInterface { /** 车辆识别标识头 */ private final String[] ALG_TAG = new String[]{"C10001"}; @Autowired private OilEventMapper oilEventMapper; @Autowired private CameraMapper cameraMapper; @Autowired private OilRecordMapper oilRecordMapper; @Autowired private ICarInfoService carInfoService; @Autowired private SysCarModelMapper sysCarModelMapper; @Autowired private OilPositionMapper oilPositionMapper; @Autowired private MqttCommandReceiver mqttCommandReceiver; @Value("${init.sendCommandTopic:/ty/aibox/command/}") private String sendCommandTopic; @Value("${init.carInOutTimeout:1800000}") private Long carInOutTimeout; @Value("${init.carOilTimeThreshold:60000}") private Long carOilTimeThreshold; /** 轻量级解决并发请求导致数据重复问题 */ private final Set platNumberSet = new ConcurrentHashSet<>(); /** 车辆在当前事务中,第一次出现在加油区的时间,platNumber_orgCode:time */ private final Map firstOilTimeMap = new ConcurrentHashMap<>(); private final List carCache = new ArrayList<>(); double handleScore = 0.9; double discardScore = 0.6; @Override public String[] getAlgTag(){ return ALG_TAG; } /** * 处理入口 * @param eventInfoDTO * @param sn * @param time */ @Override public void handle(EventInfoDTO eventInfoDTO, String sn,String time) { //当前事件唯一标识 String eventCode = eventInfoDTO.getEventCode(); //检测算法编码 String algCode = eventInfoDTO.getAlgCode(); //终端编码 String cameraCode = eventInfoDTO.getCameraCode(); //扩展属性 JSONObject extend = new JSONObject(); Object extendObj = eventInfoDTO.getExtend(); if(extendObj instanceof String){ extend = JSONObject.parseObject(extendObj.toString()); }else if(extendObj instanceof JSONObject){ extend = (JSONObject)extendObj; } log.info("车辆识别事件 eventCode:{},extend:{}",eventCode,extendObj); Device device = DeviceCache.getDeviceBySn(sn); if(ObjectUtil.isNull(device)){ log.error("网关设备不存在,sn:{}",sn); return; } JSONArray carArr = extend.getJSONArray("cars"); if(carArr.size() == 0){ return; } long timeLong = System.currentTimeMillis(); if (StringUtils.isBlank(time)) { timeLong = Long.valueOf(timeLong); } vehicleIdentificationHandle(eventCode,cameraCode,carArr,device, timeLong); } /** * 车辆识别处理 */ private void vehicleIdentificationHandle(String eventCode, String cameraCode, JSONArray carArr, Device device, long time){ for(Object obj : carArr){ JSONObject infoObj = (JSONObject) obj; //车辆识别 //车牌号 String plateNumber = infoObj.getString("number"); //过滤置信度低的数据-------------------- double score = infoObj.getDoubleValue("score"); if (score < discardScore) { log.info("检测到存在车牌号置信度较低,丢弃 {}====>{}", plateNumber, score); continue; } if(StringUtils.isEmpty(plateNumber) || plateNumber.length() < 7){ continue; } //加油位 String oilPosition = infoObj.getString("oilPosition"); if (StringUtils.isNotBlank(oilPosition)) { List positions = oilPositionMapper.selectList(new QueryWrapper<>(new OilPosition().setPositionCode(oilPosition))); oilPosition = positions.size() > 0 ? positions.get(0).getOilPosition() : oilPosition; } String carType = infoObj.getString("type"); String carReqStr = new StringBuilder().append(plateNumber).append("_").append(device.getOrgCode()).toString(); if(platNumberSet.contains(carReqStr)){ //存在并发请求,同加油站、同车辆请求还未处理完,跳过 log.info("存在并发请求,同加油站、同车辆请求还未处理完,跳过,carReqStr:{}",carReqStr); return; } synchronized (this){ //轻量级解决并发请求导致数据重复问题 if(platNumberSet.contains(carReqStr)){ //存在并发请求,同加油站、同车辆请求还未处理完,跳过 log.info("存在并发请求,同加油站、同车辆请求还未处理完,跳过,carReqStr:{}",carReqStr); return; } platNumberSet.add(carReqStr); } try { SysCarModel sysCarModel = sysCarModelMapper.selectOne(new QueryWrapper().eq("model_code", carType)); LambdaQueryWrapper wrapper = new LambdaQueryWrapper(); wrapper.eq(OilRecord::getLicenseNum, plateNumber) .eq(OilRecord::getOrgCode, device.getOrgCode()) .isNull(OilRecord::getEndTime); OilRecord oilRecord = oilRecordMapper.selectOne(wrapper); //查看是否有相似车牌号数据,有则使其进入更新流程--------------------- if (ObjectUtil.isNull(oilRecord)) { wrapper.clear(); wrapper.eq(OilRecord::getOrgCode, device.getOrgCode()) .eq(OilRecord::getDeviceId, device.getId()) .isNull(OilRecord::getEndTime); List recordList = oilRecordMapper.selectList(wrapper); if (recordList.size() > 0) { for (OilRecord record : recordList) { if (StringCampareUtil.scoreLCS(plateNumber, record.getLicenseNum()) > discardScore) { log.info("检测到存在相似车牌号记录{}====>{}", plateNumber, record.getLicenseNum()); oilRecord = record; plateNumber = record.getLicenseNum(); break; } } } } //------------------------------------------------------------------- Camera camera = cameraMapper.selectOne(new QueryWrapper().eq("code", cameraCode).eq("device_id", device.getId())); if (ObjectUtil.isNull(oilRecord)) { //如果新记录为出口摄像头拍到,则不新增记录 if (SystemConstant.INSTALL_ADDRESS_OUT.equals(camera.getInstallAddress().intValue())) { platNumberSet.remove(carReqStr); continue; } //新增记录,默认为停靠 if (score < handleScore) { //若置信度小于标准,则加入池中等待比较 log.info("检测到存在车牌号置信度中等 {}====>{}", plateNumber, score); platNumberSet.remove(carReqStr); joinWaitPool(eventCode, cameraCode, infoObj, device, time, plateNumber, score); continue; } oilRecord = oilRecordGen(cameraCode, plateNumber, oilPosition, device, time); oilRecord.setOilPosition(oilPosition); oilRecord.setModelCode(sysCarModel.getModelCode()); oilRecordMapper.insert(oilRecord); //请求图片 EventCodeRelOilCache.put(eventCode, oilRecord.getId()); } else { //更新进出记录 if (ObjectUtil.isNull(camera)) { log.error("抓拍摄像头获取失败,丢弃本次数据"); return; } if (SystemConstant.INSTALL_ADDRESS_OUT.equals(camera.getInstallAddress().intValue())) { //为出口抓拍到时,更新离开时间、停靠时长和停车是否规范。 oilRecordEndTimeUpdate(oilRecord); EventCodeRelOilCache.put(eventCode, oilRecord.getId()); } else if (SystemConstant.INSTALL_ADDRESS_FUEL_OIL.equals(camera.getInstallAddress().intValue())) { // 为加油位抓拍到时,则更新加油位和加油量 OilRecord tmpOilRecord = new OilRecord(); tmpOilRecord.setId(oilRecord.getId()); if (StringUtils.isNotBlank(oilPosition)) { tmpOilRecord.setOilPosition(oilPosition); } if(SystemConstant.BEHAVIOR_TYPE_TMP.equals(oilRecord.getBehavior().intValue())){ Long firstOilTime = firstOilTimeMap.get(carReqStr); if(ObjectUtil.isNull(firstOilTime)){ firstOilTimeMap.put(carReqStr,System.currentTimeMillis()); }else{ if(System.currentTimeMillis() - firstOilTime > carOilTimeThreshold){ //需求为加油位连续抓拍1分钟,则为加油行为 tmpOilRecord.setBehavior(SystemConstant.BEHAVIOR_TYPE_OIL.byteValue()); //FIXME 先随便取一个默认容量 tmpOilRecord.setOilVolume(50); if (ObjectUtil.isNotNull(sysCarModel)) { tmpOilRecord.setOilVolume(sysCarModel.getOilCapacity()); } } } } oilRecordMapper.updateById(tmpOilRecord); } } //建立车辆档案信息 CarInfo carInfo = new CarInfo(); carInfo.setLicenseNum(plateNumber); carInfo.setLicensePlace(CarPlaceCache.getPlace(plateNumber.substring(0, 2))); if (oilRecord != null) { carInfo.setImgPath(oilRecord.getImgPath()); carInfo.setCreateTime(oilRecord.getStartTime()); } if (sysCarModel != null) { carInfo.setModelId(sysCarModel.getModelCode()).setModelName(sysCarModel.getModelName()); } carInfoService.saveOrUpdate(carInfo); }finally { platNumberSet.remove(carReqStr); } } //默认自动上报图片视频 // if(EventCodeRelOilCache.count(eventCode) > 0){ // //需要请求图片 // log.info("请求图片,eventCode:{}",eventCode); // EventResourceDTO eventResourceDTO = new EventResourceDTO(SystemConstant.IMG_SOURCE_TYPE, eventCode, cameraCode); // EventResourceCommand eventResourceCommand = new EventResourceCommand(mqttCommandReceiver, eventResourceDTO, sendCommandTopic, device.getSn()); // eventResourceCommand.init(); // if (eventResourceCommand.execute() == SystemConstant.DEAL_FAIL) { // log.error("事件图片指令发送失败"); // } // } } /** * 置信度中等的信息加入比较等待池,1分钟后重新提取进行处理 */ private void joinWaitPool(String eventCode, String cameraCode, JSONObject car, Device device, long time, String plateNumber, double score) { CarScoreCacheDTO scoreCacheDTO = new CarScoreCacheDTO(); scoreCacheDTO.setEventCode(eventCode) .setCameraCode(cameraCode) .setCarInfo(car) .setLienseNum(plateNumber) .setTime(time) .setDeviceSn(device.getSn()) .setScore(score); carCache.add(scoreCacheDTO); } /** * 对比等待池中的车牌相似情况,并分组,取出每组评分最高的车牌,并将置信度置为0.9,重新加入处理流程 */ @Scheduled(fixedDelay = 60000L) public void waitHandle(){ if (carCache.size() == 0) return; log.info("开始处理缓存车牌数据==============》"); List carCache0 = new ArrayList<>(); synchronized (carCache) { carCache0.addAll(carCache); carCache.clear(); } carCache0.sort(Comparator.comparingDouble(CarScoreCacheDTO::getScore)); //分组数据 List> handleData = new ArrayList<>(); Set idxSet = new HashSet(); for (int i = 0; i < carCache0.size(); i++) { if (idxSet.contains(i)) { continue; } List carGroup = new ArrayList<>(); CarScoreCacheDTO carInfo = carCache0.get(i); carGroup.add(carInfo); String lienseNum0 = carInfo.getLienseNum(); String cameraCode0 = carInfo.getCameraCode(); for (int j = i + 1; j < carCache0.size(); j++) { if (idxSet.contains(j)) { continue; } String lienseNum = carCache0.get(j).getLienseNum(); String cameraCode = carCache0.get(j).getCameraCode(); if (!cameraCode.equals(cameraCode0)) continue; if (lienseNum0.equals(lienseNum)) { carInfo.setCount(carInfo.getCount() + 1); idxSet.add(j); continue; } //获取相似度,相似则加入同一组 double similarScore = StringCampareUtil.scoreLCS(lienseNum0, lienseNum); if (similarScore > discardScore) { carGroup.add(carCache0.get(j)); idxSet.add(j); } } handleData.add(carGroup); } carCache0.clear(); for (List groupData : handleData) { groupData.sort((o1, o2) -> o1.getScore() * o1.getCount() >= o2.getScore() * o2.getCount() ? 1 : -1); carCache0.add(groupData.get(0)); } joinHandle(carCache0); log.info("处理完成,已筛选出车牌数据==============》{}", JSON.toJSONString(carCache0)); } public void joinHandle(List carCacheList) { for (CarScoreCacheDTO car : carCacheList) { JSONArray carArr = new JSONArray(){{ add(car.getCarInfo()); }}; Device device = DeviceCache.getDeviceBySn(car.getDeviceSn()); log.info("=====> 低置信度数据重新加入流程", car.getCarInfo().toJSONString()); car.getCarInfo().put("score", handleScore); vehicleIdentificationHandle(car.getEventCode(), car.getCameraCode(), carArr, device, car.getTime()); } } /** * 车辆记录对象生成 * @param cameraCode * @param plateNumber * @param oilPosition * @param device * @param time * @return */ private OilRecord oilRecordGen(String cameraCode, String plateNumber, String oilPosition, Device device, long time){ OilRecord oilRecord = new OilRecord(); oilRecord.setCameraCode(cameraCode); oilRecord.setCreateTime(new Date()); oilRecord.setRecordDay(LocalDateTime.now().format(SystemConstant.DATE_DAY_FORMATTER)); oilRecord.setDeviceId(device.getId()); //默认停靠 oilRecord.setBehavior(SystemConstant.BEHAVIOR_TYPE_TMP.byteValue()); oilRecord.setLicenseNum(plateNumber); oilRecord.setOilPosition(oilPosition); oilRecord.setOrgCode(device.getOrgCode()); SysDepart sysDepart = DepartCache.getDepartByOrgCode(device.getOrgCode()); if(ObjectUtil.isNotNull(sysDepart)){ oilRecord.setOrgName(sysDepart.getDepartName()); } oilRecord.setStartTime(new Timestamp(time)); return oilRecord; } /** * 定时更新车辆进出状态 * 超时未检测到车辆离开时,自动设置为离开状态 */ @Scheduled(fixedDelay = 60000L) public void scheduled(){ List oilRecords = oilRecordMapper.selectList(new QueryWrapper().isNull("end_time")); for(OilRecord oilRecord:oilRecords){ if((System.currentTimeMillis() - oilRecord.getStartTime().getTime()) > carInOutTimeout){ //超时,自动设置为离开状态 log.info("车辆超时未离场,自动设置为离开状态,机构:{},车牌号:{},进场时间:{}",oilRecord.getOrgName(),oilRecord.getLicenseNum(),oilRecord.getStartTime()); oilRecordEndTimeUpdate(oilRecord); } } } /** * 更新车辆出场信息 * @param oilRecord */ private void oilRecordEndTimeUpdate(OilRecord oilRecord){ OilRecord tmpOilRecord = new OilRecord(); tmpOilRecord.setId(oilRecord.getId()); tmpOilRecord.setStandard(SystemConstant.STANDARD_OK.byteValue()); Long eventCount = oilEventMapper.selectCount(new QueryWrapper().eq("license_num",oilRecord.getLicenseNum()).eq("org_code",oilRecord.getOrgCode()).between("create_time",oilRecord.getCreateTime(),new Date())); if(ObjectUtil.isNotNull(eventCount) && eventCount > 0){ //存在相关告警则认为为存在违规 tmpOilRecord.setStandard(SystemConstant.STANDARD_ERROR.byteValue()); } tmpOilRecord.setEndTime(new Timestamp(System.currentTimeMillis())); int spandTime = (int) (tmpOilRecord.getEndTime().getTime() - oilRecord.getStartTime().getTime())/1000/60; tmpOilRecord.setSpandTime(spandTime); oilRecordMapper.updateById(tmpOilRecord); String carReqStr = new StringBuilder().append(oilRecord.getLicenseNum()).append("_").append(oilRecord.getOrgCode()).toString(); firstOilTimeMap.remove(carReqStr); } }