데이터 추출 코드
if ~exist('soccer.mp4', 'file')
error('Video file soccer.mp4 not found.');
end
video = VideoReader('soccer.mp4');
frameCount = floor(video.Duration * video.FrameRate);
firstFrame = readFrame(video);
hFig = figure;
imshow(firstFrame);
title('Drag to select ROI (double-click to confirm)');
h = imrect;
roi = wait(h);
roi = round(roi);
close(hFig);
firstFrame = imcrop(firstFrame, roi);
hFig = figure;
imshow(firstFrame);
title('Click on the yellow player''s color');
[xY, yY] = ginput(1);
yellowColor = squeeze(firstFrame(round(yY), round(xY), :));
title('Click on the green player''s color');
[xG, yG] = ginput(1);
greenColor = squeeze(firstFrame(round(yG), round(xG), :));
title('Click on the ball''s center and a point on its radius');
[xc, yc] = ginput(2);
ballRadius = sqrt((xc(1)-xc(2))^2 + (yc(1)-yc(2))^2);
cropSize = round(2 * ballRadius);
x1 = round(xc(1) - cropSize/2);
y1 = round(yc(1) - cropSize/2);
ballTemplate = imcrop(firstFrame, [x1, y1, cropSize, cropSize]);
title('Drag to select axis (goalpost) area (double-click to confirm)');
h = imrect;
axisRect = wait(h);
axisRect = round(axisRect);
axisTemplate = imcrop(firstFrame, axisRect);
axisCenterX = axisRect(1) + axisRect(3)/2;
axisCenterY0 = axisRect(2) + axisRect(4)/2;
close(hFig);
processedFrameCount = ceil(frameCount / 20);
greenCenters = zeros(processedFrameCount, 10, 2);
yellowCenter = zeros(processedFrameCount, 2);
ballCenter = zeros(processedFrameCount, 2);
axisCenterY = zeros(processedFrameCount, 1);
greenCentersCorrected = zeros(processedFrameCount, 10, 2);
yellowCenterCorrected = zeros(processedFrameCount, 2);
ballCenterCorrected = zeros(processedFrameCount, 2);
hWait = waitbar(0, 'Tracking in progress...');
outputVideo = VideoWriter('tracking_output_cropped_axis.avi');
open(outputVideo);
searchWidthAxis = axisRect(3) + 20;
frameIdx = 1;
for f = 1:20:frameCount
video.CurrentTime = (f - 1) / video.FrameRate;
frame = readFrame(video);
frame = imcrop(frame, roi);
labFrame = rgb2lab(frame);
greenColorLAB = rgb2lab(reshape(greenColor, [1,1,3]));
distGreen = sqrt(sum((labFrame - greenColorLAB).^2, 3));
maskGreen = distGreen < 15;
maskGreen = bwareaopen(maskGreen, 50);
statsG = regionprops(maskGreen, 'Centroid', 'Area');
if length(statsG) < 10
hFig = figure;
imshow(frame);
title(sprintf('Frame %d: Green players missing. Click 10 players.', f));
[xg, yg] = ginput(10);
greenCenters(frameIdx, :, 1) = xg;
greenCenters(frameIdx, :, 2) = yg;
close(hFig);
else
[~, idx] = maxk([statsG.Area], 10);
for i = 1:10
greenCenters(frameIdx, i, :) = statsG(idx(i)).Centroid;
end
end
yellowColorLAB = rgb2lab(reshape(yellowColor, [1,1,3]));
distYellow = sqrt(sum((labFrame - yellowColorLAB).^2, 3));
maskYellow = distYellow < 15;
maskYellow = bwareaopen(maskYellow, 50);
statsY = regionprops(maskYellow, 'Centroid', 'Area');
if isempty(statsY)
hFig = figure;
imshow(frame);
title(sprintf('Frame %d: Yellow player missing. Click directly.', f));
[xy, yy] = ginput(1);
yellowCenter(frameIdx, :) = [xy, yy];
close(hFig);
else
[~, maxIdx] = max([statsY.Area]);
yellowCenter(frameIdx, :) = statsY(maxIdx).Centroid;
end
grayFrame = rgb2gray(frame);
edges = edge(grayFrame, 'Canny');
[centers, radii] = imfindcircles(edges, [round(ballRadius - 5), round(ballRadius + 5)], 'Sensitivity', 0.9);
if ~isempty(centers)
ballCenter(frameIdx, :) = centers(1, :);
else
[y, x] = find(edges);
points = [x, y];
numCircles = 1;
try
idx = kmeans(points, numCircles);
clusterPoints = points(idx == 1, :);
if size(clusterPoints, 1) >= 3
p1 = clusterPoints(1, :); p2 = clusterPoints(2, :); p3 = clusterPoints(3, :);
midAB = [(p1(1) + p2(1))/2, (p1(2) + p2(2))/2];
if p2(1) - p1(1) == 0
slopeAB = inf;
else
slopeAB = (p2(2) - p1(2)) / (p2(1) - p1(1));
end
if slopeAB == 0
slopePerpAB = inf;
else
slopePerpAB = -1 / slopeAB;
end
midBC = [(p2(1) + p3(1))/2, (p2(2) + p3(2))/2];
if p3(1) - p2(1) == 0
slopeBC = inf;
else
slopeBC = (p3(2) - p3(2)) / (p3(1) - p2(1));
end
if slopeBC == 0
slopePerpBC = inf;
else
slopePerpBC = -1 / slopeBC;
end
if isinf(slopePerpAB)
x0 = midAB(1);
y0 = slopePerpBC * (x0 - midBC(1)) + midBC(2);
elseif isinf(slopePerpBC)
x0 = midBC(1);
y0 = slopePerpAB * (x0 - midAB(1)) + midAB(2);
else
A = [-slopePerpAB, 1; -slopePerpBC, 1];
B = [midAB(2) - slopePerpAB * midAB(1); midBC(2) - slopePerpBC * midBC(1)];
sol = A \ B;
x0 = sol(1);
y0 = sol(2);
end
ballCenter(frameIdx, :) = [x0, y0];
else
ballCenter(frameIdx, :) = prevBallCenter;
end
catch
ballCenter(frameIdx, :) = prevBallCenter;
end
end
prevBallCenter = ballCenter(frameIdx, :);
grayAxisTemplate = rgb2gray(axisTemplate);
xStartAxis = max(1, round(axisCenterX - searchWidthAxis/2));
xEndAxis = min(size(frame,2), round(axisCenterX + searchWidthAxis/2));
grayFrameStrip = grayFrame(:, xStartAxis:xEndAxis);
corrMapAxis = normxcorr2(grayAxisTemplate, grayFrameStrip);
[maxCorrAxis, maxIdxAxis] = max(corrMapAxis(:));
[ypeakAxis, ~] = ind2sub(size(corrMapAxis), maxIdxAxis);
yCenterAxis = ypeakAxis - (size(grayAxisTemplate,1) - 1)/2;
if frameIdx == 1 || maxCorrAxis >= 0.3
axisCenterY(frameIdx) = yCenterAxis;
prevAxisCenterY = yCenterAxis;
else
axisCenterY(frameIdx) = prevAxisCenterY;
end
yCorrection = axisCenterY(frameIdx) - axisCenterY0;
for i = 1:10
greenCentersCorrected(frameIdx, i, 1) = greenCenters(frameIdx, i, 1);
greenCentersCorrected(frameIdx, i, 2) = greenCenters(frameIdx, i, 2) - yCorrection;
end
yellowCenterCorrected(frameIdx, 1) = yellowCenter(frameIdx, 1);
yellowCenterCorrected(frameIdx, 2) = yellowCenter(frameIdx, 2) - yCorrection;
ballCenterCorrected(frameIdx, 1) = ballCenter(frameIdx, 1);
ballCenterCorrected(frameIdx, 2) = ballCenter(frameIdx, 2) - yCorrection;
waitbar(frameIdx / processedFrameCount, hWait, sprintf('%d / %d frames processed', frameIdx, processedFrameCount));
markedFrame = frame;
greenPos = squeeze(greenCentersCorrected(frameIdx, :, :));
markedFrame = insertMarker(markedFrame, greenPos, 'o', 'Color', 'green', 'Size', 5);
markedFrame = insertMarker(markedFrame, yellowCenterCorrected(frameIdx, :), 'o', 'Color', 'yellow', 'Size', 5);
markedFrame = insertMarker(markedFrame, ballCenterCorrected(frameIdx, :), 'o', 'Color', 'red', 'Size', 5);
markedFrame = insertShape(markedFrame, 'Rectangle', axisRect + [0, axisCenterY(frameIdx) - axisCenterY0, 0, 0], 'Color', 'blue', 'LineWidth', 2);
writeVideo(outputVideo, markedFrame);
frameIdx = frameIdx + 1;
end
close(hWait);
close(outputVideo);
data = [];
for idx = 1:processedFrameCount
row = [];
for i = 1:10
row = [row, squeeze(greenCentersCorrected(idx, i, :))'];
end
row = [row, yellowCenterCorrected(idx, :), ballCenterCorrected(idx, :)];
data = [data; row];
end
headers = {};
for i = 1:10
headers{end+1} = sprintf('Green%d_X', i);
headers{end+1} = sprintf('Green%d_Y', i);
end
headers{end+1} = 'Yellow_X';
headers{end+1} = 'Yellow_Y';
headers{end+1} = 'Ball_X';
headers{end+1} = 'Ball_Y';
writecell([headers; num2cell(data)], 'tracking_results_cropped_axis.xlsx');
prompt = sprintf('Enter frame number to visualize (1 to %d): ', processedFrameCount);
frameToShowIdx = input(prompt);
while ~isnumeric(frameToShowIdx) || frameToShowIdx < 1 || frameToShowIdx > processedFrameCount || mod(frameToShowIdx, 1) ~= 0
disp('Invalid input. Enter an integer between 1 and processedFrameCount.');
frameToShowIdx = input(prompt);
end
frameToShow = 1 + (frameToShowIdx - 1) * 20;
video.CurrentTime = (frameToShow - 1) / video.FrameRate;
frame = readFrame(video);
frame = imcrop(frame, roi);
markedFrame = frame;
greenPos = squeeze(greenCentersCorrected(frameToShowIdx, :, :));
markedFrame = insertMarker(markedFrame, greenPos, 'o', 'Color', 'green', 'Size', 5);
markedFrame = insertMarker(markedFrame, yellowCenterCorrected(frameToShowIdx, :), 'o', 'Color', 'yellow', 'Size', 5);
markedFrame = insertMarker(markedFrame, ballCenterCorrected(frameToShowIdx, :), 'o', 'Color', 'red', 'Size', 5);
markedFrame = insertShape(markedFrame, 'Rectangle', axisRect + [0, axisCenterY(frameToShowIdx) - axisCenterY0, 0, 0], 'Color', 'blue', 'LineWidth', 2);
figure;
imshow(markedFrame);
title(sprintf('Tracking result for frame %d (ROI cropped, axis corrected)', frameToShow));
disp('Completed! Saved to tracking_results_cropped_axis.xlsx');
분석 코드
data = readmatrix('tracking_results_cropped_axis.xlsx', 'Range', 'A2:X1716');
framecount = size(data, 1);
if framecount == 0
error('No data found in the file.');
end
averagedistances = zeros(framecount, 1);
stddistances = zeros(framecount, 1);
ballcoords = zeros(framecount, 2);
for frameidx = 1:framecount
greenpoints = reshape(data(frameidx, 1:20), [2, 10])';
ballpos = data(frameidx, 23:24);
if any(isnan(ballpos)) || any(isnan(greenpoints(:)))
averagedistances(frameidx) = NaN;
stddistances(frameidx) = NaN;
ballcoords(frameidx, :) = [NaN NaN];
continue;
end
ballcoords(frameidx, :) = ballpos;
distances = [];
for i = 1:9
for j = i+1:10
dist = norm(greenpoints(i,:) - greenpoints(j,:));
distances(end+1) = dist;
end
end
averagedistances(frameidx) = mean(distances);
stddistances(frameidx) = std(distances);
end
validframes = ~isnan(averagedistances) & ~isnan(stddistances) & ~isnan(ballcoords(:,1)) & ~isnan(ballcoords(:,2));
if sum(validframes) < 3
error('Not enough valid data points.');
end
ballcoords = ballcoords(validframes, :);
averagedistances = averagedistances(validframes);
stddistances = stddistances(validframes);
validcount = sum(validframes);
normalizedstd = (stddistances - min(stddistances)) / (max(stddistances) - min(stddistances));
colormapdata = parula(256);
coloridx = round(normalizedstd * 255) + 1;
gridcountx = 50;
gridcounty = 50;
xlimits = linspace(min(ballcoords(:,1)), max(ballcoords(:,1)), gridcountx + 1);
ylimits = linspace(min(ballcoords(:,2)), max(ballcoords(:,2)), gridcounty + 1);
figure;
subplot(1,2,1);
scatter3(ballcoords(:,1), ballcoords(:,2), averagedistances, 20, colormapdata(coloridx,:), 'filled');
xlabel('Ball X (pixels)');
ylabel('Ball Y (pixels)');
zlabel('Average Distance (pixels)');
title('3D Scatter Plot');
grid on;
view(45, 30);
hold on;
daspect([1 1 0.3]);
gridvalues = nan(gridcounty, gridcountx);
for i = 1:gridcountx
for j = 1:gridcounty
inx = ballcoords(:,1) >= xlimits(i) & ballcoords(:,1) < xlimits(i+1);
iny = ballcoords(:,2) >= ylimits(j) & ballcoords(:,2) < ylimits(j+1);
incell = inx & iny;
if any(incell)
gridvalues(j,i) = mean(normalizedstd(incell));
end
end
end
filledgrid = gridvalues;
for iter = 1:2
tempgrid = filledgrid;
for i = 1:gridcountx
for j = 1:gridcounty
if isnan(filledgrid(j,i))
neighbors = [];
for dx = -1:1
for dy = -1:1
if dx == 0 && dy == 0
continue;
end
xidx = i + dx;
yidx = j + dy;
if xidx >= 1 && xidx <= gridcountx && yidx >= 1 && yidx <= gridcounty
if ~isnan(filledgrid(yidx, xidx))
neighbors(end+1) = filledgrid(yidx, xidx);
end
end
end
end
if ~isempty(neighbors)
tempgrid(j,i) = mean(neighbors);
end
end
end
end
filledgrid = tempgrid;
end
subplot(1,2,2);
hold on;
graycolor = [0.8 0.8 0.8];
for i = 1:gridcountx
for j = 1:gridcounty
xpatch = [xlimits(i), xlimits(i+1), xlimits(i+1), xlimits(i)];
ypatch = [ylimits(j), ylimits(j), ylimits(j+1), ylimits(j+1)];
if isnan(filledgrid(j,i))
patch(xpatch, ypatch, graycolor, 'EdgeColor', 'none');
else
idx = round(filledgrid(j,i)*255) + 1;
patch(xpatch, ypatch, colormapdata(idx, :), 'EdgeColor', 'none');
end
end
end
scatter(ballcoords(:,1), ballcoords(:,2), 12, 'k', 'filled');
xlabel('Ball X (pixels)');
ylabel('Ball Y (pixels)');
title('Smoothed Normalized Std Dev Map');
axis equal;
grid on;
colormap(parula);
clim([0 1]);
colorbar.Label.String = 'Normalized Std Dev';
xlim([min(xlimits), max(xlimits)]);
ylim([min(ylimits), max(ylimits)]);
%
%선수-선수(위), 선수-공(아래)
%
try
data = readmatrix('tracking_results_cropped_axis.xlsx', 'Range', 'A2:X1716');
catch e
error('Error reading Excel file: %s', e.message);
end
numFrames = size(data, 1);
if numFrames == 0
error('No data read from Excel file. Check file content or range.');
end
avgDistances = zeros(numFrames, 1);
stdDistances = zeros(numFrames, 1);
ballPositions = zeros(numFrames, 2);
for idx = 1:numFrames
greenPos = reshape(data(idx, 1:20), [2, 10])';
ballPos = data(idx, 23:24);
if any(isnan(ballPos)) || any(isnan(greenPos(:)))
warning('Frame %d: NaN values detected. Skipping distance calculation.', idx);
continue;
end
ballPositions(idx, :) = ballPos;
distances = sqrt(sum((greenPos - ballPos).^2, 2));
avgDistances(idx) = mean(distances);
stdDistances(idx) = std(distances);
end
validIdx = ~isnan(avgDistances) & ~isnan(stdDistances) & ~isnan(ballPositions(:,1)) & ~isnan(ballPositions(:,2));
if sum(validIdx) < 3
error('Too few valid data points (%d) for fitting. Need at least 3 non-NaN points.', sum(validIdx));
end
ballPositions = ballPositions(validIdx, :);
avgDistances = avgDistances(validIdx);
stdDistances = stdDistances(validIdx);
numValidFrames = sum(validIdx);
normStd = (stdDistances - min(stdDistances)) / (max(stdDistances) - min(stdDistances));
colors = parula(256);
colorIndices = round(normStd * 255) + 1;
figure;
subplot(1,2,1);
scatter3(ballPositions(:,1), ballPositions(:,2), avgDistances, ...
20, colors(colorIndices,:), 'filled');
hold on;
colormap(parula);
c = colorbar;
c.Label.String = 'Standard Deviation of Distances (pixels)';
xlabel('Ball X Position (pixels)');
ylabel('Ball Y Position (pixels)');
zlabel('Average Distance to Ball (pixels)');
title('3D Scatter of Player Distances');
grid on;
view(45, 30);
axis equal;
subplot(1,2,2);
gridCountX = 50;
gridCountY = 50;
xEdges = linspace(min(ballPositions(:,1)), max(ballPositions(:,1)), gridCountX+1);
yEdges = linspace(min(ballPositions(:,2)), max(ballPositions(:,2)), gridCountY+1);
gridValues = nan(gridCountY, gridCountX);
for i = 1:gridCountX
for j = 1:gridCountY
inX = ballPositions(:,1) >= xEdges(i) & ballPositions(:,1) < xEdges(i+1);
inY = ballPositions(:,2) >= yEdges(j) & ballPositions(:,2) < yEdges(j+1);
inCell = inX & inY;
if any(inCell)
gridValues(j,i) = mean(normStd(inCell));
end
end
end
for iter = 1:2
tempGrid = gridValues;
for i = 1:gridCountX
for j = 1:gridCountY
if isnan(gridValues(j,i))
neighbors = [];
for dx = -1:1
for dy = -1:1
if dx == 0 && dy == 0, continue; end
xIdx = i + dx;
yIdx = j + dy;
if xIdx >= 1 && xIdx <= gridCountX && yIdx >= 1 && yIdx <= gridCountY
if ~isnan(gridValues(yIdx, xIdx))
neighbors(end+1) = gridValues(yIdx, xIdx);
end
end
end
end
if ~isempty(neighbors)
tempGrid(j,i) = mean(neighbors);
end
end
end
end
gridValues = tempGrid;
end
hold on;
grayColor = [0.8 0.8 0.8];
for i = 1:gridCountX
for j = 1:gridCountY
xPatch = [xEdges(i), xEdges(i+1), xEdges(i+1), xEdges(i)];
yPatch = [yEdges(j), yEdges(j), yEdges(j+1), yEdges(j+1)];
if isnan(gridValues(j,i))
patch(xPatch, yPatch, grayColor, 'EdgeColor', 'none');
else
colorIdx = round(gridValues(j,i) * 255) + 1;
patch(xPatch, yPatch, colors(colorIdx,:), 'EdgeColor', 'none');
end
end
end
scatter(ballPositions(:,1), ballPositions(:,2), 10, 'k', 'filled');
xlabel('Ball X Position (pixels)');
ylabel('Ball Y Position (pixels)');
title('Smoothed Normalized Std Dev Map');
axis equal;
grid on;
colormap(parula);
clim([0 1]);
colorbar.Label.String = 'Normalized Std Dev';
xlim([min(xEdges), max(xEdges)]);
ylim([min(yEdges), max(yEdges)]);