panlei
2019-06-26 a3a788801fcd2efb16d58e91f393bad8d7730b96
ruleserver/ruleToformula.go
@@ -3,6 +3,7 @@
import (
   "encoding/json"
   "fmt"
   "log"
   "sort"
   "strconv"
   "strings"
@@ -56,10 +57,17 @@
// 每个目标的参数:相似度,占比,尺寸
type Arg struct {
   id         string
   score      float64 // 区域内的目标的相似度
   proportion float64 // 区域内的目标的占比
   size       float64 // 区域内的目标的尺寸
   isYolo      bool    // 是否是yolo数据
   location   Rect    // 记下每个目标的位置参数,最后给结果装配人脸数据的时候用的到
   liker      []LikePerson
}
type LikePerson struct {
   Id    string  // 与之相似的底库人员的id
   Score float64 // 与底库人员的相似值
}
// 每个区域内的图片数据集合
@@ -73,7 +81,7 @@
   triggerLine   string
   directionLine string
   targetNum     int    // 区域内目标数量
   args          []Arg  // 区域内目标的参数集合
   args          []Arg  // 区域内目标集合
   filterData    []Arg  // 过滤后区域内目标集合
   time          string // 当前时间(用以匹配时间规则)
   keepRight     bool   // 是否靠右行
@@ -82,8 +90,10 @@
// sdk输出的图片上单个目标的数据
type PhotoMap struct {
   Rects Rect    // 矩形区域参数
   Score float64 // 相似度得分
   Rects Rect         // 矩形区域参数
   Score float64      // 相似度得分(有多大程度像一个目标。人脸,人体或车等等)
   IsYolo bool         // 是否是yolo数据
   Liker []LikePerson // 如果是人脸的话尤其是比对,应存下他跟底库的人员的相似情况 yolo的话给nil就行
}
// 从通道中获取的sdk输出的图像数据(目前主要是yolo算法的数据)
@@ -109,9 +119,17 @@
// 过规则库打上的标签
type Result struct {
   TaskId      string // 任务id
   IsYolo      bool   // 是否是yolo触发的规则
   RuleGroupId string // 规则组id
   AlarmLevel  int32 // 报警等级
   AlarmLevel  int32  // 报警等级
   RuleText    string // 文字版规则组
   Faces       []Face // 触发此规则组的人脸(人脸系列规则专用,yolo系列不需要)
}
//
type Face struct {
   Location Rect         // 人脸坐标框
   liker    []LikePerson // 相似人员(如果是单纯的人脸检测可无此项)
}
// 包含N条规则元素的一整条规则
@@ -147,6 +165,7 @@
      areaMap.CountAreaObjs(arg)
      list.areaMapList = append(list.areaMapList, areaMap)
   }
   //fmt.Println("为每个摄像机区域填充数据后的内容", list.areaMapList)
   // 将此帧数据按摄像机区域分类打包后判断是否报警
   judge(&list, arg)
@@ -170,16 +189,16 @@
   // }
   for _, obj := range arg.Photo {
      if threshold <= obj.Score &&
         size <= float64(obj.Rects.Width*obj.Rects.Height) &&
         intersectionper <= PgsInterPercent(areaPoints, obj.Rects, widthScale, heigthScale) {
      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)
         arg1 := Arg{ obj.Score,PgsInterPercent(areaPoints, obj.Rects, widthScale, heigthScale), float64(obj.Rects.Width * obj.Rects.Height),  obj.IsYolo,obj.Rects, obj.Liker}
         log.Println("放进去的arg:-------", arg1)
         a.args = append(a.args, arg1)
         a.filterData = append(a.filterData, arg1)
      }
   }
   a.time = time.Unix(time.Now().Unix(), 0).String()[11:16]
   a.keepRight = arg.KeepRight
   a.isStatic = arg.IsStatic
@@ -188,11 +207,17 @@
// 将字符串格式的坐标序列化为Point格式
func Json2points(areaPoints string) []Point {
   var pts []Point
   err := json.Unmarshal([]byte(areaPoints), &pts)
   if err != nil {
      fmt.Println("json.Unmarshal错误", err)
      panic("序列化坐标异常,程序退出")
   if areaPoints == "" {
      pts = append(pts, Point{0, 0})
      pts = append(pts, Point{0, 540})
      pts = append(pts, Point{960, 540})
      pts = append(pts, Point{960, 0})
   } else {
      err := json.Unmarshal([]byte(areaPoints), &pts)
      if err != nil {
         fmt.Println("json.Unmarshal错误", err)
         panic("序列化坐标异常,程序退出")
      }
   }
   return pts
}
@@ -294,20 +319,32 @@
// 独立任务
func singleTask(aml *AreaMapList, arg *ArgsFromSdk, groupRule *protomsg.GroupRule, taskId string) bool {
   var completeFormula string = ""
   var okSdks string = "" // 记录下触发规则的是yolo还是face
   for _, areaMap := range aml.areaMapList {
      //fmt.Println("当前规则组为---------:",groupRule)
      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)
         // 再过其他数据 这步直接得到结果(真或假) 过目标数量
         flag,sdk := transferParameters(groupRule.Rules[j], &areaMap)
         if flag == "true" {
            okSdks = okSdks + "," + sdk
         }
         if flag != "" {
            fmt.Println("得出的结果", flag)
            completeFormula = completeFormula + groupRule.Rules[j].RuleWithPre + "" + flag
         }
      }
      if completeFormula == "" {
         flag := splice1(&areaMap)
         if flag != "" {
            fmt.Println("强行拼凑一个人数是否大于0的结果", flag)
            completeFormula = flag
         }
         okSdks = okSdks + "," + "yolo"
      }
      for j := 0; j < len(groupRule.Rules); j++ {
         // 这步过的是时间规则(时间段等)
         flag := timeRuleResult(groupRule.Rules[j], &areaMap)
@@ -316,16 +353,18 @@
            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)
      //fmt.Println("这帧图像在任务下的除了持续时间外的一整条规则下的判断结果", result)
      // 由于天然或的关系,满足一个就该报警,即该帧数据对于某个任务的某个规则组应该报警
      if !result.(bool) { // 如果不符合条件,应该重置定时器元素,等符合时再开启,把key中包含任务id的timeEle都重置
         for k, timeEle := range TimeEleList {
@@ -335,7 +374,7 @@
         }
         return false
      } else {
         // 去看定时器此时是否走到0,走到0的话返回成功报警
         // 去看池子里是否有与本帧数据有关的定时器,如果有,看此时是否走到0,没有此定时器或有定时器走到0的话返回成功报警
         var flag bool = true
         for k, timeEle := range TimeEleList {
            if strings.Index(k, taskId) != -1 {
@@ -345,9 +384,24 @@
            }
         }
         if flag {
            fmt.Println("定时器报警了")
            // 过完规则后打个标签,告诉调用者本帧数据针对哪个任务哪组规则报警了
            arg.RuleResult = append(arg.RuleResult, Result{taskId, groupRule.GroupId,groupRule.AlarmLevel,groupRule.GroupText})
            fmt.Println("本帧数据符合规则")
            // 取出所有区域内经过过滤后还剩下的人脸,放进结果中
            faces := []Face{}
            for _, areaMap := range aml.areaMapList {
               if len(areaMap.filterData) > 0 {
                  for _, arg := range areaMap.filterData {
                     faces = append(faces,Face{arg.location,arg.liker})
                  }
               }
            }
            // 如果对yolo和人脸都报警,那就打两遍标签
            if strings.Contains(okSdks,"yolo") {
               // 过完规则后打个标签,告诉调用者本帧数据针对哪个任务哪组规则报警了     后加:可能还不够,还需要区分触发报警的对象,后面往es数据库插数据时要用
               arg.RuleResult = append(arg.RuleResult, Result{taskId, true,groupRule.GroupId, groupRule.AlarmLevel, groupRule.GroupText, faces})
            }
            if strings.Contains(okSdks,"face") {
               arg.RuleResult = append(arg.RuleResult, Result{taskId, false,groupRule.GroupId, groupRule.AlarmLevel, groupRule.GroupText, faces})
            }
            return true
         } else {
            return false
@@ -411,7 +465,6 @@
            }
         }
         am.targetNum = len(am.filterData) // 把符合条件的目标数量更新到targetNum字段
         //fmt.Println("筛选完后的内容:", am)
      }
   }
}
@@ -440,43 +493,92 @@
   }
}
// 冗余拼接一个目标数量大于0
func splice1(am *AreaMap) string {
   var num int = 0
   for _,data := range am.filterData{
      if data.isYolo {
         num++
      }
   }
   log.Println("看看区域内符合条件的目标数量:----------", num)
   formula := strconv.Itoa(num) + " " + ">" + "0"
   expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式
   result, _ := expression.Evaluate(nil)          // 得到数学公式的结果
   return strconv.FormatBool(result.(bool))
}
// 给数据库的规则表达式代参 args: 一条子规则,区域数据
func transferParameters(rule *protomsg.Rule, am *AreaMap) string {
func transferParameters(rule *protomsg.Rule, am *AreaMap) (string,string) {
   if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上
      if rule.SdkArgAlias == "targetNum" { // 如果参数是要区域内目标数量 即yolo
      if rule.SdkArgAlias == "targetNum" { // 如果参数是要区域内目标数量 即yolo 人脸不会有数量
         //fmt.Println("得出结果阶段", "比较的规则是:", rule)
         if rule.Operator == "" {
            return strconv.Itoa(am.targetNum) // 如果后面不跟操作符就直接返回数量  比如要跟下一个区域比较数量的就直接返回本区域的数量
            return strconv.Itoa(am.targetNum),"" // 如果后面不跟操作符就直接返回数量  比如要跟下一个区域比较数量的就直接返回本区域的数量
         }
         args := am.targetNum
         formula := strconv.Itoa(args) + " " + rule.Operator + " " + rule.SdkArgValue
         //args := am.targetNum     targetNum 已成所有目标的总数量,这里只算yolo的
         var num int = 0
         for _,data := range am.filterData{
            if data.isYolo {
               num++
            }
         }
         formula := strconv.Itoa(num) + " " + rule.Operator + " " + rule.SdkArgValue
         expression, _ := govaluate.NewEvaluableExpression(formula) // 得到数学公式
         result, _ := expression.Evaluate(nil)                      // 得到数学公式的结果
         return strconv.FormatBool(result.(bool))
         return strconv.FormatBool(result.(bool)),""
         // 加上关于算法的判断条件,不能只有关于规则的,有的算法本身就是一个规则,如个体静止,靠右行,所以,拿到当前子规则的sdkid来判断是否是那些特殊的规则
      } else if rule.SdkId == "IsStatic" { // 静止算法
         if am.isStatic {
            return "true"
            return "true","static"
         } else {
            return "false"
            return "false","static"
         }
      } else if rule.SdkId == "KeepRight" { // 靠右行算法
         if am.keepRight {
            return "true"
            return "true","keepRight"
         } else {
            return "false"
            return "false","keepRight"
         }
      } else if rule.SdkId == "FaceDetect" { // 人脸检测
         if rule.Operator == "==" || rule.Operator == ">=" || rule.Operator == "<=" || rule.Operator == "<" || rule.Operator == ">" || rule.Operator == "!=" {
            // 如果是不规矩的连接符统统返回false 规则也只能判断人脸的相似度,所以不存在别的连接符
            return "false"
            return "false","face"
         } else {
            return "false"
            return "false","face"
         }
      } else if rule.SdkId == "FaceCompare" {
         // 只需要过滤阈值,过滤完后数组长度大于0即为报警,但要考虑如何对每一张都报警呢
         argValue, err := strconv.ParseFloat(rule.SdkArgValue, 64)
         if err != nil {
            log.Println("规则配置的阈值非法")
            return "false","face"
         }
         flag := "false"
         for _, obj := range am.filterData {
            if !obj.isYolo {  // 人脸数据才过滤相似者
               for index := 0; index < len(obj.liker); {
                  // 将达不到阈值的相似者从相似者数组中删除
                  if obj.liker[index].Score < argValue {
                     // Go 语言中切片删除元素的本质是:以被删除元素为分界点,将前后两个部分的内存重新连接起来。不用怀疑,数组删除元素就这么坑爹
                     obj.liker = append(obj.liker[:index], obj.liker[index+1:]...)
                  } else {
                     index++
                  }
               }
            }
         }
         for _,obj2 := range am.filterData {
            if !obj2.isYolo && len(obj2.liker) > 0 {
               flag = "true"
            }
         }
         return flag,"face"
      }
   }
   return ""
   return "",""
}
func timeRuleResult(rule *protomsg.Rule, am *AreaMap) string {
   if rule.PolygonId == am.areaId { // 首先规则所对应的区域id要跟区域数据的id对的上