边缘跟踪算法实现过程
网上大部分关于边缘跟踪的算法不是目标跟踪领域,而是串接目标的边缘,和区域生长法作用一样。极少有文献是关于目标跟踪领域的边缘跟踪算法。
边缘跟踪算法主要包括4个步骤:
- 帧间波门内差分;
- 波门内阈值分割;
- 波门内去噪声;
- 计算边缘点(中心)
前面三步和质心跟踪算法一样,第4步如果是计算单个边缘点,就是单边缘跟踪,如果是计算边缘点的中心,就是双边缘中心跟踪。这里以双边缘中心跟踪为例。
帧间波门内差分
相邻帧之间在波门内进行差分操作。为了仅体现相邻帧之间的变化量,这里我做了绝对值差分操作。即
D
i
f
f
x
,
y
=
∣
f
x
,
y
i
+
1
?
f
x
,
y
i
∣
Diff_{x,y} = |f^{i+1}_{x,y} - f^i_{x,y}|
Diffx,y?=∣fx,yi+1??fx,yi?∣
f
i
+
1
f^{i+1}
fi+1和f^i是相邻帧的波门框定图像。x, y表示具体坐标。通过绝对差分可以表征帧间目标的变化量。具体函数如下:
cv::absdiff(gray_image(_roi), this->gray_image(_roi), diff);
波门内阈值分割
在得到差分图像以后,有可能差分图像本身的对比度较低。我们需要对差分图像进行二值化来进一步体现目标的变化量。一般的图像二值化可以固定一个阈值比如127u,但是差分图像需要自适应地选择阈值来进行分割。此处选择最大类间方差算法来选择分割阈值。具体思想可以参见:https://blog.csdn.net/zhu_hongji/article/details/80967776
int EdgeTracker::getThresholdValue(cv::Mat& image);
选择了最优阈值之后,就对图像进行二值分割。
void EdgeTracker::segmentByThreshold(cv::Mat& src, cv::Mat& dst, int threshold);
波门内去噪声
去噪声这一步是可选的,在实现里我采用腐蚀+膨胀的方法(被注释掉了),腐蚀是为了去除白噪点,膨胀是为了突出目标的变化量。结构元素3x3。
计算边缘的中心
计算边缘点的中心是根据阈值分割后的图来的,这一步思想非常简单,就是找目标的上下左右边界。形式化表述如下:
y
u
p
p
e
r
=
max
?
{
y
?
∣
?
y
<
y
?
,
0
<
x
<
W
,
I
(
x
,
y
)
=
0
}
y
l
o
w
e
r
=
min
?
{
y
?
∣
?
y
>
y
?
,
0
<
x
<
W
,
I
(
x
,
y
)
=
0
}
x
l
e
f
t
=
max
?
{
x
?
∣
?
x
<
x
?
,
0
<
y
<
H
,
I
(
x
,
y
)
=
0
}
x
r
i
g
h
t
=
min
?
{
x
?
∣
?
x
>
x
?
,
0
<
y
<
H
,
I
(
x
,
y
)
=
0
}
y_{upper} = \max \{y^* | \forall y < y^*, 0 < x < W, I(x, y) = 0 \} \\ y_{lower} = \min \{y^* | \forall y > y^*, 0 < x < W, I(x, y) = 0 \} \\ x_{left} = \max \{x^* | \forall x < x^*, 0 < y < H, I(x, y) = 0 \} \\ x_{right} = \min \{x^* | \forall x > x^*, 0 < y < H, I(x, y) = 0 \} \\
yupper?=max{y?∣?y<y?,0<x<W,I(x,y)=0}ylower?=min{y?∣?y>y?,0<x<W,I(x,y)=0}xleft?=max{x?∣?x<x?,0<y<H,I(x,y)=0}xright?=min{x?∣?x>x?,0<y<H,I(x,y)=0} 其中,
y
u
p
p
e
r
y_{upper}
yupper?,
y
l
o
w
e
r
y_{lower}
ylower?分别表示目标的上下边界的纵坐标,
x
l
e
f
t
x_{left}
xleft?,
x
r
i
g
h
t
x_{right}
xright?分别表示目标的左右边界的横坐标,
W
W
W和
H
H
H分别为图像的宽度和高度。
那么可以得到4个边缘点的坐标为:
p
u
p
p
e
r
=
(
x
l
e
f
t
+
x
r
i
g
h
t
2
,
y
u
p
p
e
r
)
p
l
o
w
e
r
=
(
x
l
e
f
t
+
x
r
i
g
h
t
2
,
y
l
o
w
e
r
)
p
l
e
f
t
=
(
x
l
e
f
t
,
y
u
p
p
e
r
+
y
l
o
w
e
r
2
)
p
r
i
g
h
t
=
(
x
r
i
g
h
t
,
y
u
p
p
e
r
+
y
l
o
w
e
r
2
)
p_{upper} = (\frac{x_{left} + x_{right}}{2}, y_{upper}) \\ p_{lower} = (\frac{x_{left} + x_{right}}{2}, y_{lower}) \\ p_{left} = (x_{left}, \frac{y_{upper} + y_{lower}}{2}) \\ p_{right} = (x_{right}, \frac{y_{upper} + y_{lower}}{2})
pupper?=(2xleft?+xright??,yupper?)plower?=(2xleft?+xright??,ylower?)pleft?=(xleft?,2yupper?+ylower??)pright?=(xright?,2yupper?+ylower??)
如果只利用其中一个边缘点,那么可以导出单边缘跟踪算法,这里介绍双边缘跟踪算法,实际上就是计算这4个点的中心坐标, 也就是
p
c
e
n
t
e
r
=
(
x
l
e
f
t
+
x
r
i
g
h
t
2
,
y
u
p
p
e
r
+
y
l
o
w
e
r
2
)
p_{center} = (\frac{x_{left} + x_{right}}{2}, \frac{y_{upper} + y_{lower}}{2})
pcenter?=(2xleft?+xright??,2yupper?+ylower??)
由
p
c
e
n
t
e
r
p_{center}
pcenter?的偏移可以导出目标在不同帧的移动路径,并由此确定当前帧目标的波门。
头文件如下:
class EdgeTracker : CustomTracker
{
public:
EdgeTracker(bool debug = false);
~EdgeTracker();
void init(cv::Mat& image, const cv::Rect_<int> &roi);
cv::Rect_<int> update(cv::Mat& image);
cv::Mat getDiff(cv::Mat& gray_image);
int getThresholdValue(cv::Mat& image);
void segmentByThreshold(cv::Mat& src, cv::Mat& dst, int threshold);
vector<cv::Point_<int>> findEdgePoints(cv::Mat& seg_map);
protected:
cv::Rect_<float> _roi;
cv::Mat image;
cv::Mat gray_image;
bool DEBUG = false;
vector<cv::Point_<int>> edge_points;
int ite_num;
bool first_ite;
};
核心代码如下:
vector<cv::Point_<int>> EdgeTracker::findEdgePoints(cv::Mat& seg_map)
{
int x0, y0, x1, y1;
x0 = 0;
y0 = 0;
x1 = _roi.width-1;
y1 = _roi.height-1;
int width = seg_map.cols;
int height = seg_map.rows;
bool flag1 = false, flag2 = false;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
uint8_t pix0 = seg_map.ptr<uint8_t>(i)[j];
uint8_t pix1 = seg_map.ptr<uint8_t>(height-i-1)[j];
if (pix0 == 255 && !flag1)
{
y0 = i;
flag1 = true;
}
if (pix1 == 255 && !flag2)
{
y1 = height-i-1;
flag2 = true;
}
if (flag1 && flag2)
{
flag1 = flag2 = false;
goto P1;
}
}
}
P1: for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
uint8_t pix0 = seg_map.ptr<uint8_t>(j)[i];
uint8_t pix1 = seg_map.ptr<uint8_t>(j)[width-i-1];
if (pix0 == 255)
{
x0 = i;
flag1 = true;
}
if (pix1 == 255)
{
x1 = width-i-1;
flag2 = true;
}
if (flag1 && flag2) goto P2;
}
}
P2:
vector<cv::Point_<int>> points;
cv::Point_<int> upper, lower, left, right;
upper.x = (x0 + x1) / 2;
upper.y = y0;
lower.x = (x0 + x1) / 2;
lower.y = y1;
left.x = x0;
left.y = (y0 + y1) / 2;
right.x = x1;
right.y = (y0 + y1) / 2;
points.push_back(upper);
points.push_back(lower);
points.push_back(left);
points.push_back(right);
return points;
}
|