<-- Home |--matlab |--statistics |--aggregation

世界四强平均脸的Matlab实现

英国 法国 西班牙 阿根廷

平均

截止2026年7月28日,马云和我的平均资产达到133亿美元。

内涵

平均,是一个非常激进也经常被批评的统计学概念。 平均抹去单个数据中蕴含的丰富信息,无视单个数据的特定故事, 把多个数据作为一个集合来考虑,忽略差异,用单一数据来表达整个集合。 这是一个非常强的概念。

平均,这个动作必须涉及到一个对象集合,这个集合至少应该有2个元素。对于古老的平均概念,特别是原始的平均,这个集合的元素个数必须为正整数,因此这个集合是有限可数的。

$$\{x_1, x_2, \ldots, x_n\}, n - 1 \in \mathbb{Z^+}$$

算术平均数是最常用的平均数,它是集合中所有元素的和除以元素个数。

$$ \bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i $$

隐藏一个集合$\{x_1, x_2, \ldots, x_n\}, n - 1 \in \mathbb{Z^+}$,提供单一的聚合量$\bar{x}$,体现了人类思维的抽象能力,忘却差异,归纳总结,排除细节,把个体数据特点置于背景之中,得到一个更有意义的整体信息。

算术平均数的应用

在英联邦体系中,长度的定义,foot,英尺。这个单词的原义是脚,也就是一英尺等于一个人的脚的长度。显然,每个人的脚的长度都不一样。那么应该选择英国国王的脚?那么每次更换国王是不是还需要重新丈量已有的土地?还是选择哪个领主的脚?还是选择哪个农民的脚?显然,这中单个个体的选择方法全部都是没有意义的(可以由反证法很容易得到)。并且,每个人的脚还会随着年龄发生变化。16世纪中期的雅各布·科贝尔,给出一个非常实用的定义:在教堂礼拜之后留下16为市民代表,让他们脚挨着脚站在一起,测量整个长度,然后分为16份,得到一个平均的脚长。

雅各布·科贝尔的英尺

被平均的意义

从最内心的感觉和情感体验上看,平均的激进让很多人都不适应,每个人都觉得自己已经被平均了,自己的独特性、个性、特殊性全部不见了,简直是让人生气,如果不是绝望的话。的确,平均的唯一支撑理念就是:通过忘记、忽略,提供更少的信息,从而得到更加有意义的信息。概括得到的信息超越个体。要接受“更少的信息反而是更多的信息”,需要有很强的心理承受能力。

那么对于个体而言,被平均本身有没有什么意义呢?每一个个体都交叉的属于不同的集合(团体),那么这个团体的各种平均量有没有意义呢?是不是我们也在依赖各种各种的聚合量(包括平均值)来获取心理支撑呢?

1980年,我国城乡居民人均肉类消费为12.70kg;1990年增长到15.91kg;2000年为20.22kg;2011年为28.20kg;2025年,这个数据达到37.9kg1。在我出生的前10年(1980-1990),假设我的智商和知识面神奇地达到后互联网时代的水平,我可能会很容易感觉到自己被平均了,因为我整年都吃不到任何肉食;但是自从2000年之后,我的体重开始随着肉类摄入的整体增长而增长;到2011年,我爱人已经对我的体重增长趋势产生了严重的担忧;现在,我爱人甚至开始限制我的肉类摄入量。

总体而言,对个别的样本,平均值会掩盖其个体真实情况;但是平均值的趋势变化,会逐步反应到几乎所有的个体之上。

Matlab的mean函数2

函数帮助

我们已经知道了平均的内涵和意义,那么如何在计算机中实现平均呢?在Matlab中,提供了mean函数来计算平均值。

1A = rand(10, 1);
2mean(A)

这是最简单的平均值计算方法,mean函数可以计算向量的平均值,也可以计算矩阵的平均值。甚至mean函数还能对三维以上的数组进行平均值计算。

1M = mean(A)
2M = mean(A,"all")
3M = mean(A,dim)
4M = mean(A,vecdim)
5M = mean(___,outtype)
6M = mean(___,missingflag)
7M = mean(___,Weights=W)

这些是mean函数的不同用法,大概的含义可以很容易才出来,具体可以参考Matlab文档2

加权平均

$$ \bar{x} = \frac{\sum_{i=1}^{n}w_ix_i}{\sum_{i=1}^{n}w_i} \tag{1} $$

这里的$w_i$是权重,$x_i$是数据,$n$是数据个数。

实际上,最小二乘法也是一种平均方法,它是通过最小化误差平方和来求解最优参数,从而得到一个最优值,但是也可以从平均的角度来理解最小二乘法。

对于数据集合$\{(x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)\}$,我们可以通过最小二乘法来拟合一条直线$y = kx + b$,其中$k$是斜率,$b$是截距。

$$ \begin{split} \hat{k} &= \frac{\sum_{i=1}^{n} (x_i - \bar{x})(y_i - \bar{y})}{\sum_{i=1}^{n} (x_i - \bar{x})^2} \\ \hat{b} &= \bar{y} - \hat{k}\bar{x} \\ \end{split} $$

这里的$\hat{k}$可以写为:

$$ \hat{k} = \frac{ \sum_{i=1}^{n} (x_i - \bar{x})^2 \frac{(y_i - \bar{y})}{(x_i - \bar{x})} }{ \sum_{i=1}^{n} (x_i - \bar{x})^2 }\tag{2} $$

很容易观察到(2)式与(1)式的类似之处。 实际上,最小二乘法就是对数据点的斜率进行加权平均,权重为$(x_i - \bar{x})^2$,斜率为$\frac{(y_i - \bar{y})}{(x_i - \bar{x})}$。

四强

平均人

19世纪,均值已经在天文学、测地学等领域取得绝对的支配地位。 但是在社会学中,均值还是一个非常激进的概念,甚至被认为是一个非常不科学的概念。因为社会学中,个体差异非常大,平均值可能会掩盖个体的真实情况。

比利时的统计学家阿道夫·凯特勒(Adolphe Quetelet)在19世纪中期提出了“平均人”的概念,他认为通过对大量个体的测量,可以得到一个“平均人”,这个“平均人”可以代表整个群体的特征。这个概念在当时引起了很大的争议,因为它忽略了个体差异,甚至被认为是对个体的不尊重。

实际上,平均人是一个社会中不存在的人,但是,每个种群都有自己的平均人。这个观念被批评和攻击是非常容易理解的……毕竟……你懂的。

到19世纪70年代,弗朗西斯·高尔顿(Francis Galton)采用均值的思想来处理和分析非定量数据,通过叠加肖像照片,得到一个“平均脸”,这个“平均脸”可以代表整个群体的特征。这个概念在当时也引起了很大的争议……

下面,我们也来搞一点那啥的研究,得到什么结论你别管……

世界四强平均脸

我们在看世界杯的时候就经常讨论,特别是今年挪威队成绩特别好,而蠕动的阿根廷居然进四强简直是逆天……而大热门法国、英国、西班牙也不负众望……其中法国的绝对核心,从以前人见人爱的齐达内到没有存在感的里贝里,再到现在的姆巴佩……英国对虽然有哈里凯恩作为绝对核心,但是队员的构成跟贝克汉姆的时代已经发生了翻天覆地的变化……西班牙的核心现在是亚马尔……

实际上,这个话题还是挺敏感也挺明显的……就是最优秀的黑人运动员逐步也要占领足球的主流地位了……

下面是四强合照……我随便在网上找的,也没有太讲究,不知道是真的还是游戏建模……

英国 法国 西班牙 阿根廷

我们就干脆用Matlab来计算一下四强的平均脸吧……

App3

这个程序实际还是非常先进的,利用Matlab提供的人脸识别功能,自动识别人脸,并且计算平均脸。

程序的核心算法包括三个部分:

  1. 人脸识别,采用vision.CascadeObjectDetector4来完成;
  2. 人脸对齐,采用imcrop5和imresize6来完成;
  3. 平均脸计算,采用mean2函数来完成。

用户界面设计非常简单,上方提供按钮来操作导入图像、检测人脸、计算平均、退出程序,下方提供一个图像显示区域来显示导入的图像、检测到的人脸、计算得到的平均脸。

App 初始界面

程序提供了检测人脸之后,在图像上把人脸按序号标记出来。

App 运行界面

有些时候,会产生错误的检测结果,程序提供了检查功能,可以查看检测到的人脸,并且可以反选删除错误的人脸,自动重新计算平均脸。

App 运行界面-检查

程序的代码如下,也可以通过3下载。

  1classdef SimpleFaceAverageApp < matlab.apps.AppBase
  2    % SimpleFaceAverageApp
  3    % Modern MATLAB app using UIFigure and the App Framework.
  4    % This is a new implementation and does not modify the original GUIDE version.
  5    properties (Access = public)
  6        UIFigure matlab.ui.Figure = matlab.ui.Figure.empty
  7    end
  8    properties (Access = private)
  9        MainGrid matlab.ui.container.GridLayout = matlab.ui.container.GridLayout.empty
 10        HeaderPanel matlab.ui.container.Panel = matlab.ui.container.Panel.empty
 11        HeaderLabel matlab.ui.control.Label = matlab.ui.control.Label.empty
 12        StatusLabel matlab.ui.control.Label = matlab.ui.control.Label.empty
 13        LoadButton matlab.ui.control.Button = matlab.ui.control.Button.empty
 14        DetectButton matlab.ui.control.Button = matlab.ui.control.Button.empty
 15        AverageButton matlab.ui.control.Button = matlab.ui.control.Button.empty
 16        CloseButton matlab.ui.control.Button = matlab.ui.control.Button.empty
 17        ContentGrid matlab.ui.container.GridLayout = matlab.ui.container.GridLayout.empty
 18        InputPanel matlab.ui.container.Panel = matlab.ui.container.Panel.empty
 19        InputAxes matlab.ui.control.UIAxes = matlab.ui.control.UIAxes.empty
 20        ResultPanel matlab.ui.container.Panel = matlab.ui.container.Panel.empty
 21        ResultAxes matlab.ui.control.UIAxes = matlab.ui.control.UIAxes.empty
 22        InstructionLabel matlab.ui.control.Label = matlab.ui.control.Label.empty
 23        ImageData {mustBeNumeric} = []
 24        FaceBoxes double = zeros(0, 4)
 25        FaceIncluded logical = false(0, 1)
 26        FaceTable matlab.ui.control.Table = matlab.ui.control.Table.empty
 27        AveragedImage uint8 = uint8([])
 28        LastResultClickTime double = NaN
 29        InitialFolder char = '';
 30        Detector vision.CascadeObjectDetector = vision.CascadeObjectDetector
 31    end
 32
 33    methods (Access = public)
 34        % Public entry point: create and show the app window.
 35        function app = SimpleFaceAverageApp()
 36            app.createUI();
 37            app.InitialFolder = app.getAppFolder();
 38        end
 39    end
 40
 41    methods (Access = private)
 42
 43        function createUI(app)
 44            app.UIFigure = uifigure('Name', 'Simple Face Average', ...
 45                'Position', [100 100 1180 760], ...
 46                'Color', [0.97 0.97 0.97], ...
 47                'Resize', 'on');
 48            app.UIFigure.CloseRequestFcn = @(src, event)app.closeApp();
 49
 50            app.MainGrid = uigridlayout(app.UIFigure, [2 1]);
 51            app.MainGrid.RowHeight = {132, '1x'};
 52            app.MainGrid.ColumnWidth = {'1x'};
 53            app.MainGrid.Padding = [20 20 20 20];
 54            app.MainGrid.RowSpacing = 14;
 55            app.MainGrid.ColumnSpacing = 14;
 56
 57            app.HeaderPanel = uipanel(app.MainGrid, 'Title', '');
 58            app.HeaderPanel.Layout.Row = 1;
 59            app.HeaderPanel.Layout.Column = 1;
 60            app.HeaderPanel.BackgroundColor = [1 1 1];
 61
 62            app.HeaderLabel = uilabel(app.HeaderPanel, ...
 63                'Text', 'Simple Face Average', ...
 64                'FontSize', 20, ...
 65                'FontWeight', 'bold', ...
 66                'Position', [16 78 260 24]);
 67
 68            app.LoadButton = uibutton(app.HeaderPanel, ...
 69                'Text', 'Load Image', ...
 70                'ButtonPushedFcn', @(src, event)app.loadImage(), ...
 71                'Position', [16 38 100 26]);
 72
 73            app.DetectButton = uibutton(app.HeaderPanel, ...
 74                'Text', 'Detect Faces', ...
 75                'ButtonPushedFcn', @(src, event)app.detectFaces(), ...
 76                'Position', [124 38 100 26]);
 77
 78            app.AverageButton = uibutton(app.HeaderPanel, ...
 79                'Text', 'Create Average Face', ...
 80                'ButtonPushedFcn', @(src, event)app.createAverageFace(), ...
 81                'Position', [232 38 146 26]);
 82
 83            app.CloseButton = uibutton(app.HeaderPanel, ...
 84                'Text', 'Close', ...
 85                'ButtonPushedFcn', @(src, event)app.closeApp(), ...
 86                'Position', [392 38 90 26]);
 87
 88            app.StatusLabel = uilabel(app.HeaderPanel, ...
 89                'Text', 'Load an image to begin.', ...
 90                'Position', [16 12 560 18]);
 91
 92            app.ContentGrid = uigridlayout(app.MainGrid, [1 2]);
 93            app.ContentGrid.Layout.Row = 2;
 94            app.ContentGrid.Layout.Column = 1;
 95            app.ContentGrid.ColumnWidth = {'1x', '1x'};
 96            app.ContentGrid.ColumnSpacing = 16;
 97            app.ContentGrid.Padding = [0 0 0 0];
 98
 99            app.InputPanel = uipanel(app.ContentGrid, 'Title', 'Input Image');
100            app.InputPanel.Layout.Row = 1;
101            app.InputPanel.Layout.Column = 1;
102            app.InputPanel.BackgroundColor = [1 1 1];
103
104            innerInputGrid = uigridlayout(app.InputPanel, [2 1]);
105            innerInputGrid.RowHeight = {'fit', '1x'};
106            innerInputGrid.ColumnWidth = {'1x'};
107            innerInputGrid.Padding = [12 12 12 12];
108            innerInputGrid.RowSpacing = 8;
109
110            app.InstructionLabel = uilabel(innerInputGrid, ...
111                'Text', 'Choose an image, detect faces, and build an average.', ...
112                'Position', [12 12 420 22]);
113            app.InstructionLabel.Layout.Row = 1;
114            app.InstructionLabel.Layout.Column = 1;
115
116            app.InputAxes = uiaxes(innerInputGrid);
117            app.InputAxes.Layout.Row = 2;
118            app.InputAxes.Layout.Column = 1;
119            app.InputAxes.XTick = [];
120            app.InputAxes.YTick = [];
121            app.InputAxes.Box = 'on';
122
123            app.ResultPanel = uipanel(app.ContentGrid, 'Title', 'Average Face');
124            app.ResultPanel.Layout.Row = 1;
125            app.ResultPanel.Layout.Column = 2;
126            app.ResultPanel.BackgroundColor = [1 1 1];
127
128            innerResultGrid = uigridlayout(app.ResultPanel, [3 1]);
129            innerResultGrid.RowHeight = {'fit', '1x', 170};
130            innerResultGrid.ColumnWidth = {'1x'};
131            innerResultGrid.Padding = [12 12 12 12];
132            innerResultGrid.RowSpacing = 8;
133
134            resultLabel = uilabel(innerResultGrid, ...
135                'Text', 'The average face will appear here after processing.');
136            resultLabel.Layout.Row = 1;
137            resultLabel.Layout.Column = 1;
138
139            app.ResultAxes = uiaxes(innerResultGrid);
140            app.ResultAxes.Layout.Row = 2;
141            app.ResultAxes.Layout.Column = 1;
142            app.ResultAxes.XTick = [];
143            app.ResultAxes.YTick = [];
144            app.ResultAxes.Box = 'on';
145
146            app.FaceTable = uitable(innerResultGrid);
147            app.FaceTable.Layout.Row = 3;
148            app.FaceTable.Layout.Column = 1;
149            app.FaceTable.ColumnName = {'Face', 'Include', 'Bounds'};
150            app.FaceTable.ColumnEditable = [false true false];
151            app.FaceTable.CellEditCallback = @(src, event)app.onFaceTableEdited(event);
152            app.FaceTable.Data = app.buildEmptyFaceTable();
153            app.ResultAxes.ButtonDownFcn = @(src, event)app.onResultFaceClicked();
154
155            app.showPlaceholder();
156        end
157
158        function loadImage(app)
159            [fileName, folder] = uigetfile({ '*.jpg;*.jpeg;*.png;*.bmp;*.webp', 'Image files'; '*.*', 'All files' }, ...
160                'Select an image file', app.InitialFolder);
161            if isequal(fileName, 0)
162                return;
163            end
164
165            fullFileName = fullfile(folder, fileName);
166            app.ImageData = app.readImageFile(fullFileName);
167
168            app.FaceBoxes = [];
169            app.FaceIncluded = false(0, 1);
170
171            app.showImageOnInputAxes(app.ImageData);
172            app.showPlaceholderTable();
173            app.StatusLabel.Text = sprintf('Loaded %s', fileName);
174        end
175
176        function appFolder = getAppFolder(app)
177            classFile = which(class(app));
178            if isempty(classFile)
179                appFolder = pwd;
180                return;
181            end
182
183            appFolder = fileparts(classFile);
184        end
185
186        function detectFaces(app)
187            if isempty(app.ImageData)
188                msgbox('Load an image first.', 'No image', 'warn');
189                return;
190            end
191
192            % Detect candidate face regions using MATLAB's cascade detector.
193            app.FaceBoxes = step(app.Detector, app.ImageData);
194
195            if isempty(app.FaceBoxes)
196                msgbox('No faces were detected in the selected image.', 'No faces', 'warn');
197                app.StatusLabel.Text = 'No faces detected.';
198                app.refreshFaceTable();
199                return;
200            end
201
202            app.FaceIncluded = true(size(app.FaceBoxes, 1), 1);
203            app.refreshFaceTable();
204
205            app.renderDetectedFaces();
206            app.StatusLabel.Text = sprintf('Detected %d face(s).', size(app.FaceBoxes, 1));
207        end
208
209        function createAverageFace(app)
210            if isempty(app.ImageData)
211                msgbox('Load an image first.', 'No image', 'warn');
212                return;
213            end
214
215            if isempty(app.FaceBoxes)
216                app.detectFaces();
217            end
218
219            if isempty(app.FaceBoxes)
220                return;
221            end
222
223            includedFaces = find(app.FaceIncluded);
224            if isempty(includedFaces)
225                msgbox('Enable at least one face in the table before averaging.', 'No faces selected', 'warn');
226                app.StatusLabel.Text = 'No faces selected for averaging.';
227                return;
228            end
229
230            app.updateAverageFacePreview(includedFaces);
231            app.StatusLabel.Text = 'Average face created.';
232        end
233
234        function updateAverageFacePreview(app, includedFaces)
235            if nargin < 2
236                includedFaces = find(app.FaceIncluded);
237            end
238
239            if isempty(includedFaces)
240                app.AveragedImage = uint8([]);
241                cla(app.ResultAxes, 'reset');
242                placeholderHandle = imshow(zeros(200, 200, 3, 'uint8'), 'Parent', app.ResultAxes);
243                placeholderHandle.ButtonDownFcn = @(src, event)app.onResultFaceClicked();
244                axis(app.ResultAxes, 'image');
245                title(app.ResultAxes, 'Average face');
246                return;
247            end
248
249            resizedFaces = [];
250            % Core algorithm step 1: crop each selected face and normalize size.
251            for faceIndex = includedFaces(:)'
252                k = faceIndex;
253                crop = imcrop(app.ImageData, app.FaceBoxes(k, :));
254                crop = imresize(crop, [60 NaN]);
255                crop = im2double(crop);
256                [irow, icol, ~] = size(crop);
257                temp = reshape(crop, irow * icol, 3);
258                resizedFaces = cat(3, resizedFaces, temp);
259            end
260
261            if isempty(resizedFaces)
262                app.StatusLabel.Text = 'No face data available.';
263                return;
264            end
265
266            % Core algorithm step 2: average all selected faces pixel-wise in RGB.
267            meanImage = mean(resizedFaces, 3);
268            meanImage = reshape(meanImage, irow, icol, 3);
269            meanImage = im2uint8(meanImage);
270            meanImage = imresize(meanImage, 10);
271            app.AveragedImage = meanImage;
272
273            cla(app.ResultAxes, 'reset');
274            averageImageHandle = imshow(meanImage, 'Parent', app.ResultAxes);
275            averageImageHandle.ButtonDownFcn = @(src, event)app.onResultFaceClicked();
276            axis(app.ResultAxes, 'image');
277            title(app.ResultAxes, 'Average face');
278        end
279
280        function closeApp(app)
281            if isvalid(app.UIFigure)
282                delete(app.UIFigure);
283            end
284        end
285
286        function showImageOnInputAxes(app, imageData)
287            cla(app.InputAxes, 'reset');
288            imshow(imageData, 'Parent', app.InputAxes);
289            axis(app.InputAxes, 'image');
290            title(app.InputAxes, 'Input image');
291        end
292
293        function renderDetectedFaces(app)
294            app.showImageOnInputAxes(app.ImageData);
295
296            if isempty(app.FaceBoxes)
297                return;
298            end
299
300            hold(app.InputAxes, 'on');
301            for k = 1:size(app.FaceBoxes, 1)
302                faceBox = app.FaceBoxes(k, :);
303                isIncluded = isempty(app.FaceIncluded) || app.FaceIncluded(k);
304
305                if isIncluded
306                    edgeColor = [1 0 0];
307                    labelBackground = [1 0 0];
308                    labelText = [1 1 1];
309                else
310                    edgeColor = [0.5 0.5 0.5];
311                    labelBackground = [0.5 0.5 0.5];
312                    labelText = [1 1 1];
313                end
314
315                rectangle(app.InputAxes, 'Position', faceBox, ...
316                    'EdgeColor', edgeColor, 'LineWidth', 2);
317
318                text(app.InputAxes, faceBox(1), max(faceBox(2) - 8, 1), sprintf('%d', k), ...
319                    'Color', labelText, ...
320                    'BackgroundColor', labelBackground, ...
321                    'FontWeight', 'bold', ...
322                    'FontSize', 11, ...
323                    'Margin', 1, ...
324                    'VerticalAlignment', 'bottom', ...
325                    'Clipping', 'on');
326            end
327            hold(app.InputAxes, 'off');
328        end
329
330        function showPlaceholder(app)
331            cla(app.InputAxes, 'reset');
332            cla(app.ResultAxes, 'reset');
333            app.AveragedImage = uint8([]);
334            app.LastResultClickTime = NaN;
335            imshow(zeros(200, 200, 3, 'uint8'), 'Parent', app.InputAxes);
336            axis(app.InputAxes, 'image');
337            title(app.InputAxes, 'Input image');
338            placeholderHandle = imshow(zeros(200, 200, 3, 'uint8'), 'Parent', app.ResultAxes);
339            placeholderHandle.ButtonDownFcn = @(src, event)app.onResultFaceClicked();
340            axis(app.ResultAxes, 'image');
341            title(app.ResultAxes, 'Average face');
342            app.showPlaceholderTable();
343        end
344
345        function onResultFaceClicked(app)
346            if isempty(app.AveragedImage)
347                return;
348            end
349
350            currentClickTime = posixtime(datetime('now'));
351
352            if isnan(app.LastResultClickTime)
353                app.LastResultClickTime = currentClickTime;
354                return;
355            end
356
357            elapsedTime = currentClickTime - app.LastResultClickTime;
358            app.LastResultClickTime = currentClickTime;
359
360            if elapsedTime > 0.35
361                return;
362            end
363
364            [saveFileName, saveFolder] = uiputfile({'*.png', 'PNG Image (*.png)'}, ...
365                'Export average face as PNG', ...
366                fullfile(app.InitialFolder, 'average_face.png'));
367            if isequal(saveFileName, 0)
368                return;
369            end
370
371            outputPath = fullfile(saveFolder, saveFileName);
372            imwrite(app.AveragedImage, outputPath, 'png');
373            app.StatusLabel.Text = sprintf('Saved average face: %s', saveFileName);
374            app.LastResultClickTime = NaN;
375        end
376
377        function showPlaceholderTable(app)
378            app.FaceTable.Data = app.buildEmptyFaceTable();
379        end
380
381        function faceTableData = buildEmptyFaceTable(app)
382            faceTableData = table(string.empty(0, 1), false(0, 1), string.empty(0, 1), ...
383                'VariableNames', {'Face', 'Include', 'Bounds'});
384        end
385
386        function imageData = readImageFile(app, fullFileName)
387            imageData = imread(fullFileName);
388
389            imageData = app.normalizeImageChannels(imageData);
390        end
391
392        function imageData = normalizeImageChannels(~, imageData)
393            % Keep downstream processing consistent by always working in RGB.
394            if ismatrix(imageData)
395                imageData = repmat(imageData, 1, 1, 3);
396                return;
397            end
398
399            if ndims(imageData) == 3 && size(imageData, 3) == 1
400                imageData = repmat(imageData, 1, 1, 3);
401                return;
402            end
403
404            if ndims(imageData) == 3 && size(imageData, 3) >= 3
405                imageData = imageData(:, :, 1:3);
406                return;
407            end
408
409            error('SimpleFaceAverageApp:UnsupportedImageShape', ...
410                'Unsupported image shape %s.', mat2str(size(imageData)));
411        end
412
413        function refreshFaceTable(app)
414            if isempty(app.FaceBoxes)
415                app.FaceTable.Data = app.buildEmptyFaceTable();
416                return;
417            end
418
419            faceCount = size(app.FaceBoxes, 1);
420            if numel(app.FaceIncluded) ~= faceCount
421                app.FaceIncluded = true(faceCount, 1);
422            end
423
424            faceNames = strings(faceCount, 1);
425            faceNames(:) = compose("Face %d", (1:faceCount)');
426            boundsText = strings(faceCount, 1);
427            for k = 1:faceCount
428                boundsText(k) = sprintf('[%d %d %d %d]', ...
429                    round(app.FaceBoxes(k, 1)), ...
430                    round(app.FaceBoxes(k, 2)), ...
431                    round(app.FaceBoxes(k, 3)), ...
432                    round(app.FaceBoxes(k, 4)));
433            end
434
435            app.FaceTable.Data = table(faceNames, app.FaceIncluded, boundsText, ...
436                'VariableNames', {'Face', 'Include', 'Bounds'});
437        end
438
439        function onFaceTableEdited(app, event)
440            if isempty(app.FaceBoxes)
441                return;
442            end
443
444            rowIndex = event.Indices(1);
445            if rowIndex < 1 || rowIndex > numel(app.FaceIncluded)
446                return;
447            end
448
449            newValue = event.NewData;
450            if islogical(newValue) || isnumeric(newValue)
451                app.FaceIncluded(rowIndex) = logical(newValue);
452                app.renderDetectedFaces();
453                includedFaces = find(app.FaceIncluded);
454                app.updateAverageFacePreview(includedFaces);
455                app.StatusLabel.Text = sprintf('Face %d include set to %s.', rowIndex, string(app.FaceIncluded(rowIndex)));
456            end
457        end
458    end
459end

文章标签

|-->mean |-->average |-->aggregation |-->fifa |-->average_faces |-->world cup |-->argentina |-->france |-->spanish |-->england


GitHub