背景说明
在视觉项目中,经常要判断目标的状态,例如:符号的不同频率闪烁、常亮等。然而常规的视觉算法例如YOLO,仅仅只能获取当前帧是否存在该符号,而无法对于符号状态进行判断,然而重新写一个基于时序的卷积神经网络又未免太过了,而且效果也往往低于预期。
所以笔者通过借鉴操作系统的状态转换策略,想了一个符号状态的状态机转换算法。
算法难点说明
对于该算法的主要难点如下
对于以YOLO为例的视觉检测算法传递的只有当前帧的符号类别列表,而且是非常快速的传递,状态判断算法很难直接融入到主程序当中,只能进行模块解耦。
对于视觉检测算法,必然会存在检测错误的情况(误检、漏检,错检),此时就会产生“噪声”,我们的状态判断算法必须要有足够的抗噪能力,然而对于如何进行抗噪又是一大难题。
状态机算法说明
误识别情况说明:
- 目标符号被短暂地识别为其他符号
- 其他符号被短暂地识别为目标符号
图的说明
对于所有的符号,定义模型识别到该符号记为positive,没有识别到该符号记为negative。(这里单纯指的是识别的结果)
符号共有4种状态:状态0、状态1、状态2、状态3。
3种表现形式:暗、常亮、闪烁。
所有的符号初始化为状态0、暗。
对于状态0的符号:
- 连续识别到该符号3次以上(即positive三次以上),切换为状态1,并记为常亮。
- 没有识别到该符号,保持状态不变
对于状态1的符号:
- 连续没有识别到该符号3次以上(即negative三次以上),切换为状态2。
- 连续识别到该符号,保持状态不变
对于状态2的符号:
- 连续识别到该符号3次以上(即positive三次以上),切换为状态3,并记为闪烁。
- 连续没有识别到该符号3次以上(即negative四次以上),切换为状态0,并记为暗。
- 停留在状态2时长超过2s将会进行状态的坍缩,会坍缩到上一个状态,有可能是状态2,也有可能是状态3
对于状态3的符号:
-
连续没有识别到该符号3次以上(即negative三次以上),切换为状态2。
-
连续识别到该符号5次以上(即positive五次以上),切换为状态1,并记为常亮
闪烁频率判断算法
对于闪烁频率的判断,由于检测的频率和性能的问题,实际上比较复杂,算法中采用的是100ms收集一次识别结果的方式。
例如:
对于400ms闪烁的情况:
- 理想情况:0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 ……
- 实际情况:0 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 ……
对于200ms闪烁的情况:
- 理想情况:0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 ……
- 实际情况:0 0 0 1 0 0 1 1 1 0 0 1 1 0 1 1 1 0 1 1 ……
下图是对于闪烁频率判断的具体操作方式
代码示例
下列为状态机与频率计算算法
class Label:
#初始化
def __init__(self):
self.frequency = 0 #记录闪烁频率
self.isLight = False #常亮标志
self.isFlashing = False #闪烁标志
self.id = 0 #符号ID
self.status = 0 #临时状态
self.posCount = 0 #检测到1计数
self.negCount = 0 #检测到0计数
self.start_time = 0 #用以判断频率
self.flag_time = 0 #用以判断是否是0之后的第一个1
#计数次数
def count(self,flag):
if(flag == 0):
self.negCount = self.negCount + 1
self.posCount = 0
self.flag_time = 0
#检测到该符号
if(flag == 1):
#0之后的第一个1
if(self.flag_time == 0):
self.flag_time = 1
temp_time = time.time()
self.frequency = float(temp_time - self.start_time)*1000 #单位ms
self.start_time = temp_time
self.posCount = self.posCount + 1
self.negCount = 0
#刷新状态
def refresh(self):
#详情请见confluence常亮和闪烁状态切换页面
if(self.status == 0):
#连续positive5次---->状态1,常亮
if(self.posCount >= 5):
self.isLight = True
self.isFlashing = False
self.status = 1
self.posCount = 0
self.negCount = 0
elif(self.status == 1):
#当处于状态1时,negative3次---->状态2,常亮
if(self.negCount >= 3):
self.status = 2
self.posCount = 0
self.negCount = 0
elif(self.status == 2):
#当处于状态2时,negative10次---->状态0,暗
if(self.negCount >= 10):
self.status = 0
self.isFlashing = False
self.isLight = False
self.posCount = 0
self.negCount = 0
#当处于状态2时,positive4次---->状态3,闪烁
if(self.posCount >= 4):
self.status = 3
self.isFlashing = True
self.isLight = False
self.posCount = 0
self.negCount = 0
elif(self.status == 3):
#当处于状态3时,negative4次---->状态2,闪烁
if(self.negCount >= 4):
self.status = 2
self.posCount = 0
self.negCount = 0
#当处于状态3时,posCount10次---->状态1,常亮
if(self.posCount >= 10):
self.status = 1
self.isLight = True
self.isFlashing = False
self.posCount = 0
self.negCount = 0