track_t CTrack::CalcDistCenter(const CRegion& reg) const
{
Point_t diff = m_predictionPoint - reg.m_rrect.center;
return sqrtf(sqr(diff.x) + sqr(diff.y));
}
track_t CTrack::CalcDistRect(const CRegion& reg) const
{
std::array diff;
diff[0] = reg.m_rrect.center.x - m_lastRegion.m_rrect.center.x;
diff[1] = reg.m_rrect.center.y - m_lastRegion.m_rrect.center.y;
diff[2] = static_cast(m_lastRegion.m_rrect.size.width - reg.m_rrect.size.width);
diff[3] = static_cast(m_lastRegion.m_rrect.size.height - reg.m_rrect.size.height);
diff[4] = static_cast(m_lastRegion.m_rrect.angle - reg.m_rrect.angle);
track_t dist = 0;
for (size_t i = 0; i < diff.size(); ++i)
{
dist += sqr(diff[i]);
}
return sqrtf(dist);
}
track_t CTrack::CalcDistJaccard(const CRegion& reg) const
{
track_t intArea = static_cast((reg.m_brect & m_lastRegion.m_brect).area());
track_t unionArea = static_cast(reg.m_brect.area() + m_lastRegion.m_brect.area() - intArea) + 1e-6;
return std::fabs(1 - intArea / unionArea);
}
track_t CTrack::CalcDistHist(const RegionEmbedding& embedding) const
{
track_t res = 1;
if (!embedding.m_hist.empty() && !m_regionEmbedding.m_hist.empty())
{
#if (((CV_VERSION_MAJOR == 4) && (CV_VERSION_MINOR < 1)) || (CV_VERSION_MAJOR == 3))
res = static_cast(cv::compareHist(embedding.m_hist, m_regionEmbedding.m_hist, CV_COMP_BHATTACHARYYA));
//res = 1.f - static_cast(cv::compareHist(hist, m_regionEmbedding.m_hist, CV_COMP_CORREL));
#else
res = static_cast(cv::compareHist(embedding.m_hist, m_regionEmbedding.m_hist, cv::HISTCMP_BHATTACHARYYA));
#endif
}
else
{
assert(0);
CV_Assert(!embedding.m_hist.empty());
CV_Assert(!m_regionEmbedding.m_hist.empty());
}
return res;
}
track_t CTrack::CalcMahalanobisDist(const cv::RotatedRect& rrect) const
{
cv::Mat res1, predictPoint;
// res1 = Hn * Pn+1|n+1 * Hn^T + Rn+1 error covariance
// res2 = Hn * Xn+1|n
m_kalman.GetPtStateAndResCov(res1, predictPoint);
double mahaDist = 0.0;
if (!res1.empty() && !predictPoint.empty())
{
cv::Mat icovar_Pn;
cv::invert(res1, icovar_Pn, cv::DECOMP_SVD);
cv::Mat measurePoint;
if (predictPoint.rows == 2) // PointUpdate
measurePoint = (cv::Mat_(2, 1) << rrect.center.x, rrect.center.y); // detection
else
measurePoint = (cv::Mat_(4, 1) << rrect.center.x, rrect.center.y, rrect.size.width, rrect.size.height); // predict
mahaDist = cv::Mahalanobis(measurePoint, predictPoint, icovar_Pn);
mahaDist += std::log(cv::determinant(res1));
}
return static_cast(mahaDist);
}
std::pair CTrack::CalcCosine(const RegionEmbedding& embedding) const
{
track_t res = 1;
if (!embedding.m_embedding.empty() && !m_regionEmbedding.m_embedding.empty())
{
double xy = embedding.m_embedding.dot(m_regionEmbedding.m_embedding);
double norm = sqrt(embedding.m_embDot * m_regionEmbedding.m_embDot) + 1e-6;
#if 0
res = 1.f - 0.5f * fabs(static_cast(xy / norm));
#else
res = 0.5f * static_cast(1.0 - xy / norm);
#endif
//std::cout << "CTrack::CalcCosine: " << embedding.m_embedding.size() << " - " << m_regionEmbedding.m_embedding.size() << " = " << res << std::endl;
return { res, true };
}
else
{
//assert(0);
//CV_Assert(!embedding.m_embedding.empty());
//CV_Assert(!m_regionEmbedding.m_embedding.empty());
return { 0, false };
}
}
(请注意,并非所有这些都满足 时间约束。)
上面的matlab实现,代码见『2.1Matlab代码』
的 https://www.mathworks.com/matlabcentral/fileexchange/6543-functions-for-the-rectangular-assignment-problem
地址为:https://web.archive.org/web/20120816044907/http://www.mathworks.com/matlabcentral/fileexchange/11609-hungarian-algorithm/content/Hungarian.m
function [Matching,Cost] = Hungarian(Perf)
%
% [MATCHING,COST] = Hungarian_New(WEIGHTS)
%
% A function for finding a minimum edge weight matching given a MxN Edge
% weight matrix WEIGHTS using the Hungarian Algorithm.
%
% An edge weight of Inf indicates that the pair of vertices given by its
% position have no adjacent edge.
%
% MATCHING return a MxN matrix with ones in the place of the matchings and
% zeros elsewhere.
%
% COST returns the cost of the minimum matching
% Written by: Alex Melin 30 June 2006
% Initialize Variables
Matching = zeros(size(Perf));
% Condense the Performance Matrix by removing any unconnected vertices to
% increase the speed of the algorithm
% Find the number in each column that are connected
num_y = sum(~isinf(Perf),1);
% Find the number in each row that are connected
num_x = sum(~isinf(Perf),2);
% Find the columns(vertices) and rows(vertices) that are isolated
x_con = find(num_x~=0);
y_con = find(num_y~=0);
% Assemble Condensed Performance Matrix
P_size = max(length(x_con),length(y_con));
P_cond = zeros(P_size);
P_cond(1:length(x_con),1:length(y_con)) = Perf(x_con,y_con);
if isempty(P_cond)
Cost = 0;
return
end
% Ensure that a perfect matching exists
% Calculate a form of the Edge Matrix
Edge = P_cond;
Edge(P_cond~=Inf) = 0;
% Find the deficiency(CNUM) in the Edge Matrix
cnum = min_line_cover(Edge);
% Project additional vertices and edges so that a perfect matching
% exists
Pmax = max(max(P_cond(P_cond~=Inf)));
P_size = length(P_cond)+cnum;
P_cond = ones(P_size)*Pmax;
P_cond(1:length(x_con),1:length(y_con)) = Perf(x_con,y_con);
%*************************************************
% MAIN PROGRAM: CONTROLS WHICH STEP IS EXECUTED
%*************************************************
exit_flag = 1;
stepnum = 1;
while exit_flag
switch stepnum
case 1
[P_cond,stepnum] = step1(P_cond);
case 2
[r_cov,c_cov,M,stepnum] = step2(P_cond);
case 3
[c_cov,stepnum] = step3(M,P_size);
case 4
[M,r_cov,c_cov,Z_r,Z_c,stepnum] = step4(P_cond,r_cov,c_cov,M);
case 5
[M,r_cov,c_cov,stepnum] = step5(M,Z_r,Z_c,r_cov,c_cov);
case 6
[P_cond,stepnum] = step6(P_cond,r_cov,c_cov);
case 7
exit_flag = 0;
end
end
% Remove all the virtual satellites and targets and uncondense the
% Matching to the size of the original performance matrix.
Matching(x_con,y_con) = M(1:length(x_con),1:length(y_con));
Cost = sum(sum(Perf(Matching==1)));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STEP 1: Find the smallest number of zeros in each row
% and subtract that minimum from its row
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [P_cond,stepnum] = step1(P_cond)
P_size = length(P_cond);
% Loop throught each row
for ii = 1:P_size
rmin = min(P_cond(ii,:));
P_cond(ii,:) = P_cond(ii,:)-rmin;
end
stepnum = 2;
%**************************************************************************
% STEP 2: Find a zero in P_cond. If there are no starred zeros in its
% column or row start the zero. Repeat for each zero
%**************************************************************************
function [r_cov,c_cov,M,stepnum] = step2(P_cond)
% Define variables
P_size = length(P_cond);
r_cov = zeros(P_size,1); % A vector that shows if a row is covered
c_cov = zeros(P_size,1); % A vector that shows if a column is covered
M = zeros(P_size); % A mask that shows if a position is starred or primed
for ii = 1:P_size
for jj = 1:P_size
if P_cond(ii,jj) == 0 && r_cov(ii) == 0 && c_cov(jj) == 0
M(ii,jj) = 1;
r_cov(ii) = 1;
c_cov(jj) = 1;
end
end
end
% Re-initialize the cover vectors
r_cov = zeros(P_size,1); % A vector that shows if a row is covered
c_cov = zeros(P_size,1); % A vector that shows if a column is covered
stepnum = 3;
%**************************************************************************
% STEP 3: Cover each column with a starred zero. If all the columns are
% covered then the matching is maximum
%**************************************************************************
function [c_cov,stepnum] = step3(M,P_size)
c_cov = sum(M,1);
if sum(c_cov) == P_size
stepnum = 7;
else
stepnum = 4;
end
%**************************************************************************
% STEP 4: Find a noncovered zero and prime it. If there is no starred
% zero in the row containing this primed zero, Go to Step 5.
% Otherwise, cover this row and uncover the column containing
% the starred zero. Continue in this manner until there are no
% uncovered zeros left. Save the smallest uncovered value and
% Go to Step 6.
%**************************************************************************
function [M,r_cov,c_cov,Z_r,Z_c,stepnum] = step4(P_cond,r_cov,c_cov,M)
P_size = length(P_cond);
zflag = 1;
while zflag
% Find the first uncovered zero
row = 0; col = 0; exit_flag = 1;
ii = 1; jj = 1;
while exit_flag
if P_cond(ii,jj) == 0 && r_cov(ii) == 0 && c_cov(jj) == 0
row = ii;
col = jj;
exit_flag = 0;
end
jj = jj + 1;
if jj > P_size; jj = 1; ii = ii+1; end
if ii > P_size; exit_flag = 0; end
end
% If there are no uncovered zeros go to step 6
if row == 0
stepnum = 6;
zflag = 0;
Z_r = 0;
Z_c = 0;
else
% Prime the uncovered zero
M(row,col) = 2;
% If there is a starred zero in that row
% Cover the row and uncover the column containing the zero
if sum(find(M(row,:)==1)) ~= 0
r_cov(row) = 1;
zcol = find(M(row,:)==1);
c_cov(zcol) = 0;
else
stepnum = 5;
zflag = 0;
Z_r = row;
Z_c = col;
end
end
end
%**************************************************************************
% STEP 5: Construct a series of alternating primed and starred zeros as
% follows. Let Z0 represent the uncovered primed zero found in Step 4.
% Let Z1 denote the starred zero in the column of Z0 (if any).
% Let Z2 denote the primed zero in the row of Z1 (there will always
% be one). Continue until the series terminates at a primed zero
% that has no starred zero in its column. Unstar each starred
% zero of the series, star each primed zero of the series, erase
% all primes and uncover every line in the matrix. Return to Step 3.
%**************************************************************************
function [M,r_cov,c_cov,stepnum] = step5(M,Z_r,Z_c,r_cov,c_cov)
zflag = 1;
ii = 1;
while zflag
% Find the index number of the starred zero in the column
rindex = find(M(:,Z_c(ii))==1);
if rindex > 0
% Save the starred zero
ii = ii+1;
% Save the row of the starred zero
Z_r(ii,1) = rindex;
% The column of the starred zero is the same as the column of the
% primed zero
Z_c(ii,1) = Z_c(ii-1);
else
zflag = 0;
end
% Continue if there is a starred zero in the column of the primed zero
if zflag == 1;
% Find the column of the primed zero in the last starred zeros row
cindex = find(M(Z_r(ii),:)==2);
ii = ii+1;
Z_r(ii,1) = Z_r(ii-1);
Z_c(ii,1) = cindex;
end
end
% UNSTAR all the starred zeros in the path and STAR all primed zeros
for ii = 1:length(Z_r)
if M(Z_r(ii),Z_c(ii)) == 1
M(Z_r(ii),Z_c(ii)) = 0;
else
M(Z_r(ii),Z_c(ii)) = 1;
end
end
% Clear the covers
r_cov = r_cov.*0;
c_cov = c_cov.*0;
% Remove all the primes
M(M==2) = 0;
stepnum = 3;
% *************************************************************************
% STEP 6: Add the minimum uncovered value to every element of each covered
% row, and subtract it from every element of each uncovered column.
% Return to Step 4 without altering any stars, primes, or covered lines.
%**************************************************************************
function [P_cond,stepnum] = step6(P_cond,r_cov,c_cov)
a = find(r_cov == 0);
b = find(c_cov == 0);
minval = min(min(P_cond(a,b)));
P_cond(find(r_cov == 1),:) = P_cond(find(r_cov == 1),:) + minval;
P_cond(:,find(c_cov == 0)) = P_cond(:,find(c_cov == 0)) - minval;
stepnum = 4;
function cnum = min_line_cover(Edge)
% Step 2
[r_cov,c_cov,M,stepnum] = step2(Edge);
% Step 3
[c_cov,stepnum] = step3(M,length(Edge));
% Step 4
[M,r_cov,c_cov,Z_r,Z_c,stepnum] = step4(Edge,r_cov,c_cov,M);
% Calculate the deficiency
cnum = length(Edge)-sum(r_cov)-sum(c_cov);
James Munkres' variant of the Hungarian assignment algorithm.
References:
- Matt L. Miller, Harold S. Stone, and Ingemar J. Cox. Optimizing Murty's Ranked Assignment Method. IEEE Transactions on Aerospace and Electronic Systems, 33(3), 1997
- James Munkres, Algorithms for Assignment and Transportation Problems, Journal of the Society for Industrial and Applied Mathematics Volume 5, Number 1, March, 1957
- R. A. Pilgrim. Munkres' Assignment Algorithm Modified for Rectangular Matrices http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
[assignments, unassignedTracks, unassignedDetections] = assignDetectionsToTracks(costMatrix, costOfNonAssignment)
..assignDetectionsToTracks(costMatrix,unassignedTrackCost,..unassignedDetectionCost) 分别指定未分配轨迹和检测的cost。
关闭科学计数法: MATLAB关闭科学计数法显示_Ray-Soft的博客-CSDN博客_matlab取消科学计数法
小数位个数变少: format short e
function [matches, unassignedTracks, unassignedDetections] = ...
assignDetectionsToTracks(costMatrix, costUnassignedTracks, ...
costUnassignedDetections)
%#codegen
%#ok<*EMCLS>
%#ok<*EMCA>
% Parse and check inputs
checkCost(costMatrix);
checkUnassignedCost(costUnassignedTracks, 'costUnassignedTracks');
coder.internal.errorIf(nargin == 2 && ~isscalar(costUnassignedTracks), ...
'vision:assignDetectionsToTracks:costOfNotMatchingMustBeScalar');
coder.internal.errorIf(~isa(costMatrix, class(costUnassignedTracks)), ...
'vision:assignDetectionsToTracks:allInputsMustBeSameClass');
if nargin > 2
checkUnassignedCost(costUnassignedDetections, ...
'costUnassignedDetections');
coder.internal.errorIf(~isa(costMatrix, class(costUnassignedDetections)), ...
'vision:assignDetectionsToTracks:allInputsMustBeSameClass');
coder.internal.errorIf(~isscalar(costUnassignedTracks) && ...
(numel(costUnassignedTracks) ~= size(costMatrix, 1)), ...
'vision:assignDetectionsToTracks:costUnmatchedTracksInvalidSize');
coder.internal.errorIf(~isscalar(costUnassignedDetections) && ...
(numel(costUnassignedDetections) ~= size(costMatrix, 2)), ...
'vision:assignDetectionsToTracks:costUnmatchedDetectionsInvalidSize');
end
theClass = class(costMatrix);
costUnmatchedTracksVector = ones(1, size(costMatrix, 1), theClass) .* ...
costUnassignedTracks;
if nargin > 2
costUnmatchedDetectionsVector = ones(1, size(costMatrix, 2), theClass) ...
.* costUnassignedDetections;
else
costUnmatchedDetectionsVector = ones(1, size(costMatrix, 2), theClass) .*...
costUnassignedTracks;
end
[matches, unassignedTracks, unassignedDetections] = ...
cvalgAssignDetectionsToTracks(costMatrix, costUnmatchedTracksVector, ...
costUnmatchedDetectionsVector);
%-------------------------------------------------------------------------
function tf = checkCost(cost)
validateattributes(cost, {'numeric'}, ...
{'real', 'nonsparse', 'nonnan', '2d'}, ...
'assignDetectionsToTracks', 'cost');
tf = true;
%-------------------------------------------------------------------------
function tf = checkUnassignedCost(val, varName)
validateattributes(val, {'numeric'}, ...
{'vector', 'finite', 'real', 'nonsparse'}, ...
'assignDetectionsToTracks', varName);
tf = true;
%-------------------------------------------------------------------------
function [matches, unmatchedTracks, unmatchedDetections] = ...
cvalgAssignDetectionsToTracks(cost, costUnmatchedTracks, ...
costUnmatchedDetections)
% add dummy rows and columns to account for the possibility of
% unassigned tracks and observations
paddedCost = getPaddedCost(cost, costUnmatchedTracks, ...
costUnmatchedDetections);
% solve the assignment problem
[rowInds, colInds] = find(hungarianAssignment(paddedCost));
rows = size(cost, 1);
cols = size(cost, 2);
unmatchedTracks = uint32(rowInds(rowInds <= rows & colInds > cols));
unmatchedDetections = uint32(colInds(colInds <= cols & rowInds > rows));
matches = uint32([rowInds, colInds]);
matches = matches(rowInds <= rows & colInds <= cols, :);
if isempty(matches)
matches = zeros(0, 2, 'uint32');
end
%-------------------------------------------------------------------------
function paddedCost = getPaddedCost(cost, costUnmatchedTracks,...
costUnmatchedDetections)
% replace infinities with the biggest possible number
bigNumber = getTheHighestPossibleCost(cost);
cost(isinf(cost)) = bigNumber;
% create a "padded" cost matrix, with dummy rows and columns
% to account for the possibility of not matching
rows = size(cost, 1);
cols = size(cost, 2);
paddedSize = rows + cols;
paddedCost = ones(paddedSize, class(cost)) * bigNumber;
paddedCost(1:rows, 1:cols) = cost;
for i = 1:rows
paddedCost(i, cols+i) = costUnmatchedTracks(i);
end
for i = 1:cols
paddedCost(rows+i, i) = costUnmatchedDetections(i);
end
paddedCost(rows+1:end, cols+1:end) = 0;
%-------------------------------------------------------------------------
function bigNumber = getTheHighestPossibleCost(cost)
if isinteger(cost)
bigNumber = intmax(class(cost));
else
bigNumber = realmax(class(cost));
end
%-------------------------------------------------------------------------
function assignment = hungarianAssignment(cost)
assignment = true(size(cost));
if isempty(cost)
return;
end
fprintf("原始padding cost=\n")
disp(cost)
% step 1: subtract row minima
cost = bsxfun(@minus, cost, min(cost, [], 2));
fprintf("step1-减去行最小值后: \ncost=\n")
disp(cost)
% step 2: make an initial assignment by "starring" zeros
stars = makeInitialAssignment(cost);
fprintf("step2-初始猜测 \nstar=\n")
disp(stars)
% step 3: cover all columns containing starred zeros
colCover = any(stars);
while ~all(colCover)
% uncover all rows and unprime all zeros
rowCover = false(1, size(cost, 1));
primes = false(size(stars));
Z = ~cost; % mark locations of the zeros
Z(:, colCover) = false;
while 1
shouldCreateNewZero = true;
% step 4: Find a noncovered zero and prime it.
[zi, zj] = findNonCoveredZero(Z);
while zi > 0
primes(zi, zj) = true;
% find a starred zero in the column containing the primed zero
starredRow = stars(zi, :);
if any(starredRow)
% if there is one, cover the its row and uncover
% its column.
rowCover(zi) = true;
colCover(starredRow) = false;
Z(zi, :) = false;
Z(~rowCover, starredRow) = ~cost(~rowCover, starredRow);
[zi, zj] = findNonCoveredZero(Z);
else
shouldCreateNewZero = false;
% go to step 5
break;
end
end
if shouldCreateNewZero
% step 6: create a new zero
[cost, Z] = createNewZero(cost, rowCover, colCover);
fprintf("step 6-创建新的0\n cost = \n")
disp(cost)
else
break;
end
end
% step 5: Construct a series of alternating primed and starred zeros.
stars = alternatePrimesAndStars(stars, primes, zi, zj);
fprintf("step 5-重新标记0 stars=\n")
disp(stars)
% step 3: cover all columns containing starred zeros
colCover = any(stars);
end
assignment = stars;
%-------------------------------------------------------------------------
function stars = makeInitialAssignment(cost)
rowCover = false(1, size(cost, 1));
colCover = false(1, size(cost, 2));
stars = false(size(cost));
[zr, zc] = find(cost == 0);
for i = 1:numel(zr)
if ~rowCover(zr(i)) && ~colCover(zc(i))
stars(zr(i), zc(i)) = true;
rowCover(zr(i)) = true;
colCover(zc(i)) = true;
end
end
%-------------------------------------------------------------------------
function [zi, zj] = findNonCoveredZero(Z)
fprintf("step 4- 查找Z是否有0\nZ=\n")
disp(Z)
[i, j] = find(Z, 1);
if isempty(i)
zi = -1;
zj = -1;
else
zi = i(1);
zj = j(1);
end
%-------------------------------------------------------------------------
function [cost, Z] = createNewZero(cost, rowCover, colCover)
Z = false(size(cost));
% find a minimum uncovered value
uncovered = cost(~rowCover, ~colCover);
minVal = min(uncovered(:));
% add the minimum value to all intersections of covered rows and cols
cost(rowCover, colCover) = cost(rowCover, colCover) + minVal;
% subtract the minimum value from all uncovered entries creating at
% least one new zero
cost(~rowCover, ~colCover) = uncovered - minVal;
% mark locations of all uncovered zeros
Z(~rowCover, ~colCover) = ~cost(~rowCover, ~colCover);
%-------------------------------------------------------------------------
% Step 5.
% Construct a series of alternating primed and starred zeros.
% Start with the primed uncovered zero Z0 at (zi, zj). Find a starred zero
% Z1 in the column of Z0. Star Z0, and unstar Z1. Find a primed zero Z2 in
% the row of Z1. If the are no starred zeros in the column of Z2, stop.
% Otherwise repeat with Z0 = Z2.
function stars = alternatePrimesAndStars(stars, primes, zi, zj)
nRows = size(stars, 1);
nCols = size(stars, 2);
% create a logical index of Z0
lzi = false(1, nRows);
lzj = false(1, nCols);
lzi(zi) = true;
lzj(zj) = true;
% find a starred zero Z1 in the column of Z0
rowInd = stars(1:nRows, lzj);
% star Z0
stars(lzi, lzj) = true;
while any(rowInd(:))
% unstar Z1
stars(rowInd, lzj) = false;
% find a primed zero Z2 in Z1's row
llzj = primes(rowInd, 1:nCols);
lzj = llzj(1, :);
lzi = rowInd;
% find a starred zero in Z2's column
rowInd = stars(1:nRows, lzj);
% star Z2
stars(lzi, lzj) = true;
end
例子来源于:The Algorithm Workshop - Bevilacqua Research Corporation
用上面的2.2的matlab代码跑一个简单的demo,并输出的中间过程为:
假设cost为: [1,2,3;2,4,6;3,6,9]
调用方法为:[assignments, unassignedTracks, unassignedDetections] = assignDetectionsToTracks(cost, 20);
上述代码基本上与下述的过程是一致的。
cost =
1 2 30
2 4 30
3 6 30
step 5-重新标记0 stars=
0 1 0 0 0 0
1 0 0 0 0 0
0 0 1 0 0 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
还是给第三个检测分配了第三个ID,但是第三个检测是都不匹配的,已经都超出我的距离范畴了。还在匹配,这就出现了问题。
如何fix这个问题???
目前的实现中,发现对矩阵的预处理(主要是填充)有三种处理方式:
填充方式 | 注意事项 |
不填充:scipy.optimize.linear_sum_assignment sklearn.utils.linear_assignment_ |
|
max(n,m)填充 填充全0; 给多的track或者多的detect无匹配的选择 |
|
(n+m)*(n+m)填充; 可以同时给出不分配的track和不分配的detection |
|
现在比较max(n,m)填充和(n+m)*(n+m)填充的差异:
matlab代码② padding—— padding为方阵,填0。矩阵填充后的大小为max(m,n) * max(m,n)
max(n,m) padding ——matlab代码:https://www.mathworks.com/matlabcentral/fileexchange/20652-hungarian-algorithm-for-linear-assignment-problems-v2-3?s_tid=srchtitle
假设现在的cost 矩阵如下:2×3的矩阵。
cost =
15.5841 13.1604 12.2434
15.1019 13.1676 14.1931
matlab代码② 源码如下:
function [assignment,cost] = munkres(costMat)
% MUNKRES Munkres Assign Algorithm
%
% [ASSIGN,COST] = munkres(COSTMAT) returns the optimal assignment in ASSIGN
% with the minimum COST based on the assignment problem represented by the
% COSTMAT, where the (i,j)th element represents the cost to assign the jth
% job to the ith worker.
%
% This is vectorized implementation of the algorithm. It is the fastest
% among all Matlab implementations of the algorithm.
% Examples
% Example 1: a 5 x 5 example
%{
[assignment,cost] = munkres(magic(5));
[assignedrows,dum]=find(assignment);
disp(assignedrows'); % 3 2 1 5 4
disp(cost); %15
%}
% Example 2: 400 x 400 random data
%{
n=400;
A=rand(n);
tic
[a,b]=munkres(A);
toc % about 6 seconds
%}
% Reference:
% "Munkres' Assignment Algorithm, Modified for Rectangular Matrices",
% http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
% version 1.0 by Yi Cao at Cranfield University on 17th June 2008
assignment = false(size(costMat));
cost = 0;
costMat(costMat~=costMat)=Inf;
validMat = costMat
一直在step4中,
① 若该行有新的0,但是该行有star0了,0*,取消这个star,
② 然后看取消的这个0*的列的~cost重新赋值过来看看。
③ 继续看有没有新的0*,继续考察。到第五行,发现此行没有star 0 的;那么第一个0就是star 0。
star Z0; unstar Z1;star Z2 over
我们填充的这个数值大小会不会对正确的分配造成误导?会不会产生误匹配?什么时候会产生?什么时候不会产生?
结论是:在这种场景下,是不会改变分配的结果的。只不过,算法经历了更多次的迭代!!
算法的迭代过程如下:
>> [assignments, unassignedTracks, unassignedDetections] = ...
assignDetectionsToTracks(cost, 10);
原始padding cost=
15.584 13.16 12.243 10 1.7977e+308
15.102 13.168 14.193 1.7977e+308 10
10 1.7977e+308 1.7977e+308 0 0
1.7977e+308 10 1.7977e+308 0 0
1.7977e+308 1.7977e+308 10 0 0
step1-减去行最小值后:
cost=
5.584 3.16 2.243 0 1.7977e+308
5.102 3.168 4.193 1.7977e+308 0
10 1.7977e+308 1.7977e+308 0 0
1.7977e+308 10 1.7977e+308 0 0
1.7977e+308 1.7977e+308 10 0 0
step2-初始猜测
star=
0 0 0 1 0
0 0 0 0 1
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 6-创建新的0
cost =
3.341 0.917 0 0 1.7977e+308
2.859 0.925 1.95 1.7977e+308 0
7.757 1.7977e+308 1.7977e+308 0 0
1.7977e+308 7.757 1.7977e+308 0 0
1.7977e+308 1.7977e+308 7.757 0 0
step 4- 查找Z是否有0
Z=
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
0 0 0 1 0
0 0 0 1 0
step 5-重新标记0 stars=
0 0 1 0 0
0 0 0 0 1
0 0 0 1 0
0 0 0 0 0
0 0 0 0 0
step 3-star的列有:
colCover=
0 0 1 1 1
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 6-创建新的0
cost =
2.424 0 0 0 1.7977e+308
1.942 0.008 1.95 1.7977e+308 0
6.84 1.7977e+308 1.7977e+308 0 0
1.7977e+308 6.84 1.7977e+308 0 0
1.7977e+308 1.7977e+308 7.757 0 0
step 4- 查找Z是否有0
Z=
0 1 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 6-创建新的0
cost =
2.424 0 0 0.008 1.7977e+308
1.934 0 1.942 1.7977e+308 0
6.832 1.7977e+308 1.7977e+308 0 0
1.7977e+308 6.832 1.7977e+308 0 0
1.7977e+308 1.7977e+308 7.749 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 1 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 1
0 0 0 0 1
0 0 0 0 1
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 1
step 5-重新标记0 stars=
0 0 1 0 0
0 1 0 0 0
0 0 0 0 1
0 0 0 1 0
0 0 0 0 0
step 3-star的列有:
colCover=
0 1 1 1 1
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 6-创建新的0
cost =
0.49 0 0 0.008 1.7977e+308
0 0 1.942 1.7977e+308 0
4.898 1.7977e+308 1.7977e+308 0 0
1.7977e+308 6.832 1.7977e+308 0 0
1.7977e+308 1.7977e+308 7.749 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 1 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 6-创建新的0
cost =
0.49 0 0 4.906 1.7977e+308
0 0 1.942 1.7977e+308 4.898
0 1.7977e+308 1.7977e+308 0 0
1.7977e+308 1.934 1.7977e+308 0 0
1.7977e+308 1.7977e+308 2.851 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 1
0 0 0 0 1
step 4- 查找Z是否有0
Z=
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 1
step 5-重新标记0 stars=
0 0 1 0 0
0 1 0 0 0
1 0 0 0 0
0 0 0 0 1
0 0 0 1 0
step 3-star的列有:
colCover=
1 1 1 1 1
scipy.optimize.linear_sum_assignment 源码位置:https://github.com/scipy/scipy/blob/v1.8.0/scipy/optimize/_lsap.py#L15-L86
_lsap.py 来自于sklearn的实现。
调用了如下_lsap_module模块:
return _lsap_module.calculate_assignment(cost_matrix, maximize = false)
https://github.com/scipy/scipy/blob/v1.8.0/scipy/optimize/_lsap_module.c
int ret = solve_rectangular_linear_sum_assignment(
num_rows, num_cols, cost_matrix, maximize,
PyArray_DATA((PyArrayObject*)a),
PyArray_DATA((PyArrayObject*)b));
调用 https://github.com/scipy/scipy/blob/v1.8.0/scipy/optimize/rectangular_lsap/rectangular_lsap.cpp
中的solve_retangular_linear_sum_assignment。
又调用solve函数。
最终python调用的是C代码,基于以下paper实现的。属于:最短增广路径的矩阵分配算法。
This code implements the shortest augmenting path algorithm for the
rectangular assignment problem. This implementation is based on the
pseudocode described in pages 1685-1686 of:
DF Crouse. On implementing 2D rectangular assignment algorithms.
IEEE Transactions on Aerospace and Electronic Systems
52(4):1679-1696, August 2016
doi: 10.1109/TAES.2016.140952
部分源代码如下:
scipy代码的逻辑跟KM匈牙利算法有差异,目前还没看出来差异在哪。这就是linear_sum_assignment算法的最终实现
① sklearn.utils.linear_assignment_ 在0.23.0的版本之前才有。
② 源码位置:https://github.com/scikit-learn/scikit-learn/blob/0.17.1/sklearn/utils/linear_assignment_.py
在跟踪的匹配问题上,应该使用maximum value匹配,而不是complete matching完全匹配。我们没必要做一个完全匹配。
不使用KM的完全匹配,而用mini-cut-max flow替代可能是一个比较好的选择
完全匹配——我猜测是尽可能多的匹配对;在乎大家都一个匹配起来。
而实际上,我们需要尽量多的正确匹配,不想要错误的匹配。不想要新ID一定要匹配一个不合适的track,尽量避免这种情况。这样会ID切换,跳ID跳来跳去的。
https://github.com/scikit-learn/scikit-learn/issues/13464 中有表明:sklearn在0.23以后的版本中弃用了sklearn.utils.linear_assignment_
# from sklearn.utils.linear_assignment_ import linear_assignment
from scipy.optimize import linear_sum_assignment as linear_assignment
sklearn.utils.linear_assignment_ 源码解析:
不padding,n×m矩阵。
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "kuhn_munkres.hpp"
#include
#include
#include
namespace cv {
namespace detail {
inline namespace tracking {
KuhnMunkres::KuhnMunkres() : n_() {}
std::vector KuhnMunkres::Solve(const cv::Mat& dissimilarity_matrix) {
CV_Assert(dissimilarity_matrix.type() == CV_32F);
double min_val;
cv::minMaxLoc(dissimilarity_matrix, &min_val);
CV_Assert(min_val >= 0);
n_ = std::max(dissimilarity_matrix.rows, dissimilarity_matrix.cols);
dm_ = cv::Mat(n_, n_, CV_32F, cv::Scalar(0));
marked_ = cv::Mat(n_, n_, CV_8S, cv::Scalar(0));
points_ = std::vector(n_ * 2);
dissimilarity_matrix.copyTo(dm_(
cv::Rect(0, 0, dissimilarity_matrix.cols, dissimilarity_matrix.rows)));
is_row_visited_ = std::vector(n_, 0);
is_col_visited_ = std::vector(n_, 0);
Run();
std::vector results(static_cast(marked_.rows), static_cast(-1));
for (int i = 0; i < marked_.rows; i++) {
const auto ptr = marked_.ptr(i);
for (int j = 0; j < marked_.cols; j++) {
if (ptr[j] == kStar) {
results[i] = j;
}
}
}
return results;
}
void KuhnMunkres::TrySimpleCase() {
auto is_row_visited = std::vector(n_, 0);
auto is_col_visited = std::vector(n_, 0);
for (int row = 0; row < n_; row++) {
auto ptr = dm_.ptr(row);
auto marked_ptr = marked_.ptr(row);
auto min_val = *std::min_element(ptr, ptr + n_);
for (int col = 0; col < n_; col++) {
ptr[col] -= min_val;
if (ptr[col] == 0 && !is_col_visited[col] && !is_row_visited[row]) {
marked_ptr[col] = kStar;
is_col_visited[col] = 1;
is_row_visited[row] = 1;
}
}
}
}
bool KuhnMunkres::CheckIfOptimumIsFound() {
int count = 0;
for (int i = 0; i < n_; i++) {
const auto marked_ptr = marked_.ptr(i);
for (int j = 0; j < n_; j++) {
if (marked_ptr[j] == kStar) {
is_col_visited_[j] = 1;
count++;
}
}
}
return count >= n_;
}
cv::Point KuhnMunkres::FindUncoveredMinValPos() {
auto min_val = std::numeric_limits::max();
cv::Point min_val_pos(-1, -1);
for (int i = 0; i < n_; i++) {
if (!is_row_visited_[i]) {
auto dm_ptr = dm_.ptr(i);
for (int j = 0; j < n_; j++) {
if (!is_col_visited_[j] && dm_ptr[j] < min_val) {
min_val = dm_ptr[j];
min_val_pos = cv::Point(j, i);
}
}
}
}
return min_val_pos;
}
void KuhnMunkres::UpdateDissimilarityMatrix(float val) {
for (int i = 0; i < n_; i++) {
auto dm_ptr = dm_.ptr(i);
for (int j = 0; j < n_; j++) {
if (is_row_visited_[i]) dm_ptr[j] += val;
if (!is_col_visited_[j]) dm_ptr[j] -= val;
}
}
}
int KuhnMunkres::FindInRow(int row, int what) {
for (int j = 0; j < n_; j++) {
if (marked_.at(row, j) == what) {
return j;
}
}
return -1;
}
int KuhnMunkres::FindInCol(int col, int what) {
for (int i = 0; i < n_; i++) {
if (marked_.at(i, col) == what) {
return i;
}
}
return -1;
}
void KuhnMunkres::Run() {
TrySimpleCase();
while (!CheckIfOptimumIsFound()) {
while (true) {
auto point = FindUncoveredMinValPos();
auto min_val = dm_.at(point.y, point.x);
if (min_val > 0) {
UpdateDissimilarityMatrix(min_val);
} else {
marked_.at(point.y, point.x) = kPrime;
int col = FindInRow(point.y, kStar);
if (col >= 0) {
is_row_visited_[point.y] = 1;
is_col_visited_[col] = 0;
} else {
int count = 0;
points_[count] = point;
while (true) {
int row = FindInCol(points_[count].x, kStar);
if (row >= 0) {
count++;
points_[count] = cv::Point(points_[count - 1].x, row);
int col1 = FindInRow(points_[count].y, kPrime);
count++;
points_[count] = cv::Point(col1, points_[count - 1].y);
} else {
break;
}
}
for (int i = 0; i < count + 1; i++) {
auto& mark = marked_.at(points_[i].y, points_[i].x);
mark = mark == kStar ? 0 : kStar;
}
is_row_visited_ = std::vector(n_, 0);
is_col_visited_ = std::vector(n_, 0);
marked_.setTo(0, marked_ == kPrime);
break;
}
}
}
}
}
}}} // namespace
使用Yolov5_Deepsort_pytorch复现的代码,代替官方DeepSort代码,来看效果。代码开源仓库地址为:https://github.com/mikel-brostrom/Yolov5_DeepSort_Pytorch
DeepSort对象 初始化时:reID特征提取初始化;sort跟踪器初始化。
①extractor
②tracker(sort)
deepsort.update
发现错误匹配还是非常多的!!
- 级联匹配:第一次靠外观距离(feature distance)或者运动模型距离(马氏距离)来匹配。
① 由于使用的是n*m的scipy的linear_assignment_或者sklearn的linear_sum_assignment函数,不好处理unsignedTrack和unsignedDetection情况。所以有阈值预处理超过Thresh的置为inf,再进入assign分配算法。
② 由于上面会导致一个目标前后两帧的bbox新建多个track,所以靠IOU级联匹配来打补丁。- 我计划把匹配改为单纯的运动模型距离(马氏距离)来匹配,看效果!
第一次confirmed都为0,所以都是IOU分配。IOU连续分配3次,hits超过3次,连续3次有匹配,才是confirmed,交给外观和运动模型匹配。
但是,匹配的结果为:trackID=5与detect=2匹配,trackID=3与detect=5匹配,这里就是两个错误的匹配,这是什么原因???
是cost matrix导致的,不是分配算法导致的。是距离计算矩阵出现了问题!
其cost矩阵如下所示:
导致了两个匹配的原因,在于:
① (5,2)的distance为1.6899
② (3,5)的distance为1.5159
③ (3,2)的distance为1.4994.
本来(3,2)应该选择1.4994,但是由于1.4994+1.8072 > 1.5169 + 1.6899,导致了ID交换匹配了。
也就是说,此处出现的问题为:5刚好丢失了检测,此时附近刚好又冒出一个新目标,然后扰乱了这个cost矩阵的意义了!!!!
此处,我的看法是:明明5与2距离那么远,他们的distance竟然没有想象的那么大。
也就是明明运动距离很远,但是马氏距离cost distance却差距不大!!!!!!这个是需要反思的。在漏检出现的时候,考验着cost matrix距离矩阵的鲁棒性!!!
这里可以发现,误匹配的数值是比较小的,1.68,区分度不够。是被除以了一个数,归一化过了。
track共13个,detection共10个。
detect bbox 有10个,x,y
以第一行为例,欧氏距离和马氏距离,在量纲上有差异。
对于不同的行来说,使用了不同的协方差阵做乘积和加法。这部分是影响costMatrix数值的关键。
其中我认为后面的l+log|E|的部分影响是更大的。对于一个新的检测,和轨迹而言,新检测要与存在的轨迹的差距都拉开,尽量不匹配。
那么所有的track的数值都要比较大才好。此时,在不同的log|E|的作用下,要让数值都比较大,covariance中的过程噪声和观测噪声决定了所有trackID的convarance的数值,希望让新检测的那一列的数值都很大,这样才不会匹配。
对于检测丢失的track而言,也是所有检测都远。这个自然能够避免,为什么?因为这一行的数值都大,是因为这一行的covraince都一样,只要距离的分子够大,就可以避免匹配的。
cost =
95.019 9.4303 45.392 28.86 69.685 27.527 19.995 39.792 22.234 65.094
9.4677 94.342 58.752 75.063 154.11 112.3 83.896 64.287 107.05 39.132
133.83 50.507 85.501 69.426 27.208 32.884 60.765 80.051 38.039 104.7
57.816 44.654 9.665 25.839 103.28 62.27 34.505 15.092 57.104 28.693
75.077 28.742 25.996 9.7506 88.503 46.689 18.285 20.486 41.449 45.411
107.3 22.404 58.062 41.687 56.275 14.481 32.879 52.508 9.6962 77.621
64.246 39.627 14.957 20.475 99.375 57.578 29.292 9.5085 52.316 34.558
84.049 19.83 34.951 18.457 79.542 37.734 9.7382 29.414 32.511 54.391
112.13 27.229 62.899 46.505 51.451 9.6362 37.683 57.345 14.541 82.448
40.129 64.332 28.432 44.907 124.59 82.438 53.824 34.008 77.141 10.236
111.56 31.603 65.152 49.764 44.12 14.811 41.477 59.924 19.642 83.598
148.75 70.072 103.13 87.932 14.791 53.427 79.744 97.983 58.302 121.25
143.85 65.157 98.201 83.026 9.7568 48.514 74.845 93.055 53.38 116.34
这是matlab代码的分配结果,与实际的对应一下。
以第二行的数据为例,trackID=1,即cost矩阵的第二行。与白色检测框ID的距离,在图中所示如下:
将第二行的数据与图中对应起来看,发现了运动模型距离与实际的距离是一致的。
上述匹配的结果也是准确的。
① ID=2的track,在第6帧丢失了检测框,属于unsignedTrack
② ID=12的track与检测框ID=4匹配,但是刚刚建立起来,还没显示出来。
③ ID=10和11的track也丢失了检测框。
与实际情况一致。
也就是第6帧中:detection=[0,1,2,3,4,5,6,7,8,9] 10个detection都有track与之匹配。没有没有匹配的白色检测框。
匹配结果为:
cost=[4.13,4.9,25.7,13.0,5.9,28.56;
4.9, 4.12, 23.0, 15.6, 7.5, 25.8;
25.7, 23.1, 4.12, 38, 29, 5.08;
5.8, 7.5, 28.4, 10.2, 4.13, 31.165;
15.9, 13.5, 10.9, 27.71, 19.47, 13.70;
12.8, 15.35, 37.301, 4.139, 10.317, 40.081;
17.6, 15.375, 8.1, 28.518, 20.188, 10.011]
左侧为trackID,右侧为detect序号。
ID=7应该无匹配,而ID=3应该匹配detect=2。这里匹配错了!!!!
从这里可以看出,右侧的初始分配,就差一红色框列在最底部画1,就是最优解了。
但是匈牙利分配算法给整成左边的分配格局了。左边的分配的cost》右边的分配cost的,这是为什么会产生这样的结果呢???
可能的解决方案:右边的对角线填充,与下方的对角线填充不一样的值!!why?
————————————等一下,先要确保costMatrix的合理性,才能来动这里,这里的问题晚些时候再来讨论,需要先解决前面的问题。
漏检的track:
新出现的detect:
天然的优势是:由新检测框新建的track都在末尾,出现的晚。所以最后考虑匹配它。优先考虑先把前面的行或列匹配好。
找到了问题所在!!!!!!—— thresh的值不合理,导致匹配错误,thresh的值太大了。
我把它设计为3.5之后,成功解决了大部分的匹配失误问题!!!!!!!
距离近的cost反而比距离远的cost大,这是cost矩阵计算的就不合理。kalman的covariance是否有问题???
原因:①unMatchedTrack的协方差过大,初始化时为diag[200,200];unMatchedTrack的协方差可以到几万。
②导致,costMatrix的距离很小,很容易跟一些新出现的detect bbox匹配起来。
所以关键原因在于unMatchedTrack只有kalman predict,没有kalman correct。所以这个是deepsort中的逻辑,来看一下没有correct的后果!
上面的原因在于代码写错了。应该是track的变量写到共用的kalman中去了,导致了track使用了上一个ID的变量。出现了这么奇怪的bug
这样导致的问题:
① kalman的Pn协方差,没有经过校正一步,会不断的上涨,导致cost减小,容易产生较多的误匹配。
② 经过bug修正后,预测的部分没有乱的折线了。但是静止的和穿越的时候,还是频繁切换ID。