package ruleserver import ( "encoding/json" "fmt" "sort" "strconv" "strings" "time" "basic.com/dbapi.git" "basic.com/pubsub/protomsg.git" "github.com/knetic/govaluate" ) // 任务 type Task struct { camID string //摄像机ID taskID string //任务ID sdkID string //算法ID areaId string //区域id areaName string //区域名称 topicType string //规则主题类型,目标/持续时间/灵敏度等 } // 数据库中的规则元素 type SingleRule struct { Task operatorType string // 操作符,>=,==... compareType string // 对比类型,值,被选项 compareValue string // 对比的值 ruleWithPrevious string // 跟上一条的逻辑关系 groupId string // 大规则id } // 数据库中单条子规则 跟数据库映射的 type CameraTaskArg struct { Id string `json:"id"` CameraTaskId string `json:"camera_task_id"` CameraId string `json:"camera_id"` PolygonId string `json:"polygon_id"` SdkId string `json:"sdk_id"` SdkArgAlias string `json:"sdk_arg_alias"` Operator string `json:"operator"` //操作符,>=,==... OperatorType string `json:"operator_type"` //对比类型,值,被选项 SdkArgValue string `json:"sdk_arg_value"` //对比的值 Sort int `json:"sort"` RuleWithPrevious string `json:"rule_with_previous"` //跟上一条的逻辑关系 GroupId string `json:"group_id"` //大规则id } // sdk输出的图片提取参数数据之后的数据集合,也是传给下一步用于赋值的数据集合 type AreaMapList struct { areaMapList []AreaMap } // 每个目标的参数:相似度,占比,尺寸 type Arg struct { id string score float64 // 区域内的目标的相似度 proportion float64 // 区域内的目标的占比 size float64 // 区域内的目标的尺寸 } // 每个区域内的图片数据集合 type AreaMap struct { cameraId string areaId string groupId string taskId string sdkIds []string areaJson string triggerLine string directionLine string targetNum int // 区域内目标数量 args []Arg // 区域内目标的参数集合 filterData []Arg // 过滤后区域内目标集合 time string // 当前时间(用以匹配时间规则) keepRight bool // 是否靠右行 isStatic bool // 是否静止 } // sdk输出的图片上单个目标的数据 type PhotoMap struct { Rects Rect // 矩形区域参数 Score float64 // 相似度得分 } // 从通道中获取的sdk输出的图像数据(目前主要是yolo算法的数据) type ArgsFromSdk struct { Photo []PhotoMap CameraId string TaskId string KeepRight bool // 是否靠右行 算法判断的与上一帧图像的比较结果 IsStatic bool // 是否静止 ImageWidth int // 摄像机拍摄的图像宽 像素 ImageHeight int // 摄像机拍摄的图像高 像素 RuleResult []Result // 过完规则后打的标签 } // 将传递过来的参数转化为 //protomsg.SdkMessage.TaskLabel.SdkmsgWithTask.sdkdata type ResultMsg struct { protomsg.SdkMessage RuleResult []Result // 过完规则后打的标签 } // 过规则库打上的标签 type Result struct { TaskId string // 任务id RuleGroupId string // 规则组id } // 包含N条规则元素的一整条规则 type CompleteRule struct { rule string } // 摄像机区域 跟数据库映射的 // type CameraPolygon struct { // Id string `json:"id"` // CameraId string `json:"camera_id"` // Name string `json:"name"` // Polygon string `json:"polygon"` // 坐标点区域 // TriggerLine string `json:"trigger_line"` // DirectionLine string `json:"direction_line"` // } // 根据摄像机id拿到摄像机所有区域 func GetPolygons(cameraId string) []protomsg.CameraPolygon { var api dbapi.CameraApi data := api.FindAllPolygons() //fmt.Println("查到的所有区域:", data) // 根据id从map中拿到区域 var cameraPolygons []protomsg.CameraPolygon for _, item := range data { if item.CameraId == cameraId { // 需要根据比例把前台画的区域的坐标转化为相应摄像机拍摄的图像的大小 x坐标分别*image.width/页面区域宽 y坐标分别*image.height/页面区域高 // 前台宽高固定 cameraPolygons = append(cameraPolygons, item) } } //log.Println("根据摄像机id查到的区域", cameraPolygons, "--区域数量为:", len(cameraPolygons)) return cameraPolygons } // 规则主函数入口 func (arg *ArgsFromSdk) MainJudge() { cameraPolygons := GetPolygons(arg.CameraId) list := AreaMapList{} for _, polygon := range cameraPolygons { areaMap := AreaMap{cameraId: arg.CameraId, areaId: polygon.Id, areaJson: polygon.Polygon, triggerLine: polygon.TriggerLine, directionLine: polygon.DirectionLine} // 为每个摄像机区域填充数据 areaMap.CountAreaObjs(arg) list.areaMapList = append(list.areaMapList, areaMap) } //fmt.Println("为每个摄像机区域填充数据后的内容", list.areaMapList) // 将此帧数据按摄像机区域分类打包后判断是否报警 judge(&list, arg) } // 计算区域内的目标数量以及将相似度、占比、尺寸等打包 func (a *AreaMap) CountAreaObjs(arg *ArgsFromSdk) { a.targetNum = 0 threshold := 0.0 // 相似度 intersectionper := 0.2 // 占比 size := 0.0 // 尺寸 areaPoints := Json2points(a.areaJson) widthScale := float64(arg.ImageWidth / 960) heigthScale := float64(arg.ImageHeight / 540) // for _, sdkInfo := range arg.SdkMessage.Tasklab.Sdkinfos { // if sdkInfo.Sdktype == "yolo" { // //sdkInfo.Sdkdata. // } // } for _, obj := range arg.Photo { if threshold <= obj.Score && size <= float64(obj.Rects.Width*obj.Rects.Height) && intersectionper <= PgsInterPercent(areaPoints, obj.Rects, widthScale, heigthScale) { // 这步要备齐表达式里所需要的所有参数 a.targetNum++ arg := Arg{score: obj.Score, proportion: PgsInterPercent(areaPoints, obj.Rects, widthScale, heigthScale), size: float64(obj.Rects.Width * obj.Rects.Height)} a.args = append(a.args, arg) a.filterData = append(a.filterData, arg) } } a.time = time.Unix(time.Now().Unix(), 0).String()[11:16] a.keepRight = arg.KeepRight a.isStatic = arg.IsStatic } // 将字符串格式的坐标序列化为Point格式 func Json2points(areaPoints string) []Point { var pts []Point err := json.Unmarshal([]byte(areaPoints), &pts) if err != nil { fmt.Println("json.Unmarshal错误", err) panic("序列化坐标异常,程序退出") } return pts } // 以摄像机id查出跟其相关的所有任务下的规则组 func GetRuleGroup(cameraId string) []*protomsg.TaskGroupArgs { // 查询数据库 // 第一步查出跟这个摄像机相关的group_id(大规则) var api dbapi.CameraTaskArgsApi all := api.FindAll() //fmt.Println("所有规则:", all) var taskArgs []*protomsg.TaskGroupArgs for _, taskArg := range all { if taskArg.CameraId == cameraId { taskArgs = taskArg.TaskArgs } } return taskArgs } // 联动任务的处理 func linkTask(aml *AreaMapList, arg *ArgsFromSdk, groupRule *protomsg.GroupRule, taskId string) { // new一个定时器,如果以此groupId为标志的定时器不存在的话 var flag bool = true var timeEle = TimeElement{N: 3, InitN: 3, GroupId: groupRule.GroupId} for k, timeEle1 := range TimeEleList { if k == groupRule.GroupId { flag = false // 已经有了这个定时器就置为false 不再新增 timeEle = *timeEle1 } } if flag { // 如果还没有这个定时器元素就新增一个 timeEle := TimeElement{N: 3, InitN: 3, GroupId: groupRule.GroupId} // 扔进去一个定时器元素 //TimeEleList = make(map[string]timeElement) TimeEleList[groupRule.GroupId] = &timeEle // 定时器元素以规则组id为键 fmt.Println("创建了计数器并且计数器集合为:", TimeEleList) // 得出这组完整规则里涉及到几个摄像机,决定着数组里有几个结构体,去重添加方式 for j := 0; j < len(groupRule.Rules); j++ { var flag1 bool = false for _, ruleRes := range TimeEleList[groupRule.GroupId].RuleResults { if groupRule.Rules[j].CameraId == ruleRes.CameraId { flag1 = true } } if flag1 { TimeEleList[groupRule.GroupId].RuleResults = append(TimeEleList[groupRule.GroupId].RuleResults, &RuleResult{groupRule.Rules[j].CameraId, groupRule.Rules[j].Sort, "", groupRule.Rules[j].RuleWithPre}) } } } // 往数组里赋值 isOk := singleTask(aml, arg, groupRule, taskId) if isOk { fmt.Println("这帧图像在任务下的一整条规则下(联动任务下就是跟本摄像机像相关的小规则)的判断结果为true") // 根据cameraId去更新或者插入结果,然后判断是否数组是否可以得出报警的结论 // 往联动任务的结果数组里放值或更新 for _, va := range timeEle.RuleResults { if aml.areaMapList[0].cameraId != "" && va.CameraId == aml.areaMapList[0].cameraId { va.Result = strconv.FormatBool(isOk) } } // 判断结果数组是否完满可得出报警结果 var isPerfect = true for _, va := range timeEle.RuleResults { if va.Result == "" && va.RuleWithPre != "||" { isPerfect = false } } if isPerfect { // 将数组按sort排序 sort.Sort(SubList(timeEle.RuleResults)) // 排序后取各自的结果和连接符拼出规则表达式得出结果 completeFormula := "" for _, va := range timeEle.RuleResults { completeFormula = completeFormula + va.RuleWithPre + "" + va.Result } if completeFormula != "" { expression, _ := govaluate.NewEvaluableExpression(completeFormula) result, _ := expression.Evaluate(nil) // 得到数学公式的结果 if result.(bool) { // 过完规则后打个标签,告诉调用者本帧数据针对哪个任务哪组规则报警了 arg.RuleResult = append(arg.RuleResult, Result{TaskId: taskId, RuleGroupId: groupRule.GroupId}) } } } else { fmt.Println("数组不圆满不打标签") } } else { // 没有报警, fmt.Println("这帧图像在任务下的一整条规则下(联动任务下就是跟本摄像机像相关的小规则)的判断结果为false") // 所以也要去结果数组里放值或更新 for _, va := range timeEle.RuleResults { if aml.areaMapList[0].cameraId != "" && va.CameraId == aml.areaMapList[0].cameraId { // aml.areaMapList[0].cameraId 随便找一个数据 va.Result = strconv.FormatBool(isOk) } } // 因为本帧数据不符合规则,所以也不用统计结果数组里的东西 } } // 独立任务 func singleTask(aml *AreaMapList, arg *ArgsFromSdk, groupRule *protomsg.GroupRule, taskId string) bool { var completeFormula string = "" for _, areaMap := range aml.areaMapList { for j := 0; j < len(groupRule.Rules); j++ { // 先过完条件数据 filterRule(groupRule.Rules[j], &areaMap) } for j := 0; j < len(groupRule.Rules); j++ { // 再过其他数据 这步直接得到结果(真或假) flag := transferParameters(groupRule.Rules[j], &areaMap) if flag != "" { fmt.Println("得出的结果", flag) completeFormula = completeFormula + groupRule.Rules[j].RuleWithPre + "" + flag } } for j := 0; j < len(groupRule.Rules); j++ { // 这步过的是时间规则(时间段等) flag := timeRuleResult(groupRule.Rules[j], &areaMap) if flag != "" { fmt.Println("时间规则的结果", flag) completeFormula = completeFormula + groupRule.Rules[j].RuleWithPre + "" + flag } } for j := 0; j < len(groupRule.Rules); j++ { // 最后过持续时间等时间维度的条件 duration(groupRule.Rules[j], &areaMap) } } fmt.Println("拼出的数学公式为:==== ", completeFormula) if completeFormula != "" { expression, _ := govaluate.NewEvaluableExpression(completeFormula) result, _ := expression.Evaluate(nil) // 得到数学公式的结果 fmt.Println("这帧图像在任务下的除了持续时间外的一整条规则下的判断结果", result) // 由于天然或的关系,满足一个就该报警,即该帧数据对于某个任务的某个规则组应该报警 if !result.(bool) { // 如果不符合条件,应该重置定时器元素,等符合时再开启,把key中包含任务id的timeEle都重置 for k, timeEle := range TimeEleList { if strings.Index(k, taskId) != -1 { timeEle.N = timeEle.InitN // 重置定时器 } } return false } else { // 去看定时器此时是否走到0,走到0的话返回成功报警 var flag bool = true for k, timeEle := range TimeEleList { if strings.Index(k, taskId) != -1 { if timeEle.N != 0 { // 跟这个任务有关的定时器要全部等于0 flag = false } } } if flag { fmt.Println("定时器报警了") // 过完规则后打个标签,告诉调用者本帧数据针对哪个任务哪组规则报警了 arg.RuleResult = append(arg.RuleResult, Result{TaskId: taskId, RuleGroupId: groupRule.GroupId}) return true } else { return false } } } else { return false } } // 对单帧图像的判断 是舍弃(或者说对于某些需求可以放ES数据库一份)还是返回 func judge(aml *AreaMapList, arg *ArgsFromSdk) { // 得到属于该摄像机的若干组任务的完整规则(跟每一条完整规则比较之后得出本张图像对于某个规则是否报警的结果。放进map,比如本帧图像的id,所碰撞成功的规则id) taskRuleList := GetRuleGroup(aml.areaMapList[0].cameraId) if len(taskRuleList) > 0 { for _, taskRule := range taskRuleList { ruleList := taskRule.GroupRules // 获取的是task下面的任务组 taskId := taskRule.TaskId for i := 0; i < len(ruleList); i++ { temp := ruleList[i].Rules // temp为一组完整规则 在此需要判断规则是否是联动规则 if len(temp) > 0 { if strings.Contains(ruleList[i].GroupId, "link") { // groupId中含有link则为联动任务 linkTask(aml, arg, ruleList[i], taskId) } else { // 独立任务的处理 singleTask(aml, arg, ruleList[i], taskId) } } } } } } // 过滤规则先筛选人数 func filterRule(rule *protomsg.Rule, am *AreaMap) { if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上 if rule.SdkArgAlias == "score" || rule.SdkArgAlias == "proportion" || rule.SdkArgAlias == "size" { // 判断的是相似值,占比,尺寸等过滤条件,如果再有,还可以再加 //fmt.Println("筛选人数阶段", "比较的规则是:", rule) var args []Arg if rule.RuleWithPre == "&&" { args = am.filterData } else { args = am.args } // 先清空过滤后的数据,再往里塞本次过滤后的数据 am.filterData = am.filterData[0:0] for _, arg := range args { var formula string if rule.SdkArgAlias == "score" { formula = strconv.FormatFloat(arg.score, 'f', -1, 64) + " " + rule.Operator + " " + rule.SdkArgValue // 得到字符串公式 } else if rule.SdkArgAlias == "proportion" { formula = strconv.FormatFloat(arg.proportion, 'f', -1, 64) + " " + rule.Operator + " " + rule.SdkArgValue // 得到字符串公式 } else { formula = strconv.FormatFloat(arg.size, 'f', -1, 64) + " " + rule.Operator + " " + rule.SdkArgValue // 得到字符串公式 } expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式 result, _ := expression.Evaluate(nil) // 得到数学公式的结果 if result.(bool) { am.filterData = append(am.filterData, arg) // 得到符合条件的过滤数据 } } am.targetNum = len(am.filterData) // 把符合条件的目标数量更新到targetNum字段 //fmt.Println("筛选完后的内容:", am) } } } // 都过滤完条件之后看看是否要创建一个定时器元素 创建定时器的条件:是否有靠右行,个体静止等自带定时器含义的算法以及是否有持续时间 func duration(rule *protomsg.Rule, am *AreaMap) { if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上 if rule.SdkArgAlias == "duration" { // // 先看看定时器元素队列中是否有这个摄像机这个区域的定时器,如果有就不能再次创建了 var flag bool = true for k, _ := range TimeEleList { if k == am.taskId+" "+am.areaId { flag = false // 有就置为false fmt.Println("有这个定时器,不再创建了:") } } if flag { timeLength, _ := strconv.Atoi(rule.SdkArgValue) timeEle := TimeElement{N: timeLength, InitN: timeLength} // 扔进去一个定时器元素 //TimeEleList = make(map[string]timeElement) TimeEleList[am.taskId+" "+am.areaId] = &timeEle // 定时器元素以摄像机id拼接区域id为键 fmt.Println("创建了计数器并且计数器集合为:", TimeEleList) } } } } // 给数据库的规则表达式代参 args: 一条子规则,区域数据 func transferParameters(rule *protomsg.Rule, am *AreaMap) string { if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上 if rule.SdkArgAlias == "targetNum" { // 如果参数是要区域内目标数量 //fmt.Println("得出结果阶段", "比较的规则是:", rule) if rule.Operator == "" { return strconv.Itoa(am.targetNum) // 如果后面不跟操作符就直接返回数量 比如要跟下一个区域比较数量的就直接返回本区域的数量 } args := am.targetNum formula := strconv.Itoa(args) + " " + rule.Operator + " " + rule.SdkArgValue expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式 result, _ := expression.Evaluate(nil) // 得到数学公式的结果 return strconv.FormatBool(result.(bool)) // 加上关于算法的判断条件,不能只有关于规则的,有的算法本身就是一个规则,如个体静止,靠右行,所以,拿到当前子规则的sdkid来判断是否是那些特殊的规则 } else if rule.SdkId == "个体静止" { // 暂时用汉字代替啦,晚些替换成正式的id if am.isStatic { return "true" } else { return "false" } } else if rule.SdkId == "靠右行" { // 暂时用汉字代替啦,晚些替换成正式的id if am.keepRight { return "true" } else { return "false" } } } return "" } func timeRuleResult(rule *protomsg.Rule, am *AreaMap) string { if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上 if rule.SdkArgAlias == "time" { // 判断是否符合时间规则 // 根据放值字段里存的时间规则的id去另一个表里查需要比对的时间段(比如当前时间是周三,应根据区域id查出其周三的几个布防时间段,数组) //fmt.Println("时间规则的测试") now := time.Now() index := getIndexOfWeek(now.Weekday().String()) timeList := GetTimeById(rule.SdkArgValue, index) //fmt.Println("从数据库中查出的时间规则:", timeList) // 判断图片数据的时间是否符合当前规则 在一个即为true,全不在为false flag := "false" for _, timeSlot := range timeList { if rule.Operator == "satisfy" { // 满足所选的时间规则 formula := "'" + timeSlot.Start + "'" + "<" + "'" + am.time + "'" expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式 result, _ := expression.Evaluate(nil) // 得到数学公式的结果 formula1 := "'" + timeSlot.End + "'" + ">" + "'" + am.time + "'" expression1, _ := govaluate.NewEvaluableExpression(formula1) // 得到数学公式 result1, _ := expression1.Evaluate(nil) // 得到数学公式的结果 //fmt.Println("看看这两尊大神", result, result1) if result.(bool) && result1.(bool) { flag = "true" break } } if rule.Operator == "unsatisfy" { // 不满足所选的时间规则 formula := timeSlot.Start + "<" + am.time expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式 result, _ := expression.Evaluate(nil) // 得到数学公式的结果 formula1 := timeSlot.End + ">" + am.time expression1, _ := govaluate.NewEvaluableExpression(formula1) // 得到数学公式 result1, _ := expression1.Evaluate(nil) // 得到数学公式的结果 if result.(bool) && result1.(bool) { flag = "true" break } } } return flag } } return "" } // 根据传入的字符串得到其在一周内的索引 周一到周日分别对应1到7 func getIndexOfWeek(weekday string) int { var weekdays = [7]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"} for k, value := range weekdays { if value == weekday { return k + 1 // 因为数据库中存的是1-7代表的周一到周日 } } return 0 } type TimeRange struct { Start string `json:"start"` End string `json:"end"` } type day struct { Day int `json:"day"` // 标示当前星期几 TimeRange []TimeRange `json:"time_range"` // 当天的几个时间段 } // 取出某个时间规则的第几天的规则段集合 func GetTimeById(id string, index int) []TimeRange { var cameraTimeRule protomsg.CameraTimerule var api dbapi.CameraApi _, rules := api.FindAllTimeRules() for _, rule := range rules { if rule.Id == id { cameraTimeRule = rule } } var timeRangeList []day json.Unmarshal([]byte(cameraTimeRule.TimeRule), &timeRangeList) for _, timerange := range timeRangeList { if timerange.Day == index { //log.Println("取到的时间规则:", timerange.TimeRange) return timerange.TimeRange } } return nil }