精华内容
下载资源
问答
  • 然后建立二维人脸模型和三维人脸模型的映射关系,得到三维人脸形状模型的形状参数;最后通过纹理映射得到照片中人物的真实人脸模型,当然应该会有一定的误差。 现在只是完成了特征点的定位,遇到的困难: 1、怎么...
  • 人脸三维重建就是建立人脸的三维模型,它相对于二维人脸图像多了一个维度,在电影,游戏等领域应用广泛。目前获取人脸三维模型的方法主要包括三种,软件建模,仪器采集与基于图像的建模。 (1) 软件建模作为最早的三...

    基于图像的人脸三维重建在人脸分析与娱乐领域里有巨大的应用场景,同时它也可以用于提升人脸关键点检测,人脸识别,人脸编辑等很多任务。本文重点介绍其中基于3DMM模型的核心技术及其研究进展。

    作者&编辑 | 言有三

    1. 什么是人脸三维重建

    人脸三维重建就是建立人脸的三维模型,它相对于二维人脸图像多了一个维度,在电影,游戏等领域应用广泛。目前获取人脸三维模型的方法主要包括三种,软件建模,仪器采集与基于图像的建模。

    (1) 软件建模作为最早的三维建模手段,现在仍然是最广泛地在电影,动漫行业中应用。顶顶大名的3DMax就是典型代表,作品如下图。

    (2) 由于手工建模耗费大量的人力,三维成像仪器也得到了长期的研究和发展。基于结构光和激光仪器的三维成像仪是其中的典型代表,我们熟知的iphoneX中的人脸识别就是基于结构光进行三维人脸重建,正因如此才有iphonex中的三维人脸表情包。这些基于仪器采集的三维模型,精度可达毫米级,是物体的真实三维数据,也正好用来为基于图像的建模方法提供评价数据库。不过由于仪器的成本太高,一般的用户是用不上了。

    (3) 基于图像的建模技术(image based modeling),顾名思义,是指通过若干幅二维图像,来恢复图像或场景的三维结构,这些年得到了广泛的研究。

    我们这里说的人脸三维重建,就特指基于图像的人脸三维重建方法。人脸三维重建的研究已经有几十年的历史,但是基于图像的快速高精度三维人脸重建还没有工业落地,需要研究人员继续努力。

    2. 什么是3DMM模型

    基于人脸图像的三维重建方法非常多,常见的包括立体匹配,Structure From Motion(简称SfM),Shape from Shading(简称sfs),三维可变形人脸模型(3DMM),本文就重点讲述3D Morphable models(简称3DMM),其相关的传统方法和深度学习方法都有较多的研究。

    2.1 基本思想

    3DMM,即三维可变形人脸模型,是一个通用的三维人脸模型,用固定的点数来表示人脸。它的核心思想就是人脸可以在三维空间中进行一一匹配,并且可以由其他许多幅人脸正交基加权线性相加而来。我们所处的三维空间,每一点(x,y,z),实际上都是由三维空间三个方向的基量,(1,0,0),(0,1,0),(0,0,1)加权相加所得,只是权重分别为x,y,z。

    转换到三维空间,道理也一样。每一个三维的人脸,可以由一个数据库中的所有人脸组成的基向量空间中进行表示,而求解任意三维人脸的模型,实际上等价于求解各个基向量的系数的问题。

    人脸的基本属性包括形状和纹理,每一张人脸可以表示为形状向量和纹理向量的线性叠加。

    形状向量Shape Vector:S=(X1,Y1,Z1,X2,Y2,Z2,...,Yn,Zn),示意图如下:

    纹理向量Texture Vector:T=(R1,G1,B1,R2,G2,B2,...,Rn,Bn),示意图如下:

    任意的人脸模型可以由数据集中的m个人脸模型进行加权组合如下:

    其中Si,Ti就是数据库中的第i张人脸的形状向量和纹理向量。但是我们实际在构建模型的时候不能使用这里的Si,Ti作为基向量,因为它们之间不是正交相关的,所以接下来需要使用PCA进行降维分解。

    (1) 首先计算形状和纹理向量的平均值。

    (2) 中心化人脸数据。

    (3) 分别计算协方差矩阵。

    (4) 求得形状和纹理协方差矩阵的特征值α,β和特征向量si,ti。

    上式可以转换为下式

    其中第一项是形状和纹理的平均值,而si,ti则都是Si,Ti减去各自平均值后的协方差矩阵的特征向量,它们对应的特征值按照大小进行降序排列。

    等式右边仍然是m项,但是累加项降了一维,减少了一项。si,ti都是线性无关的,取其前几个分量可以对原始样本做很好的近似,因此可以大大减少需要估计的参数数目,并不失精度。

    基于3DMM的方法,都是在求解这几个系数,随后的很多模型会在这个基础上添加表情,光照等系数,但是原理与之类似。

    2.2 3DMM模型求解方法

    基于3DMM求解三维人脸需要解决的问题就是形状,纹理等系数的估计,具体就是如何将2D人脸拟合到3D模型上,被称为Model Fitting,这是一个病态问题。经典的方法是1999年的文章"A Morphable Model For The Synthesis Of 3D Faces",其传统的求解思路被称为analysis-by-Synthesis,如下;

    (a) 初始化一个3维的模型,需要初始化内部参数α,β,以及外部渲染参数,包括相机的位置,图像平面的旋转角度,直射光和环境光的各个分量,图像对比度等共20多维,有了这些参数之后就可以唯一确定一个3D模型到2D图像的投影。

    (b) 在初始参数的控制下,经过3D至2D的投影,即可由一个3D模型得到2维图像,然后计算与输入图像的误差。再以误差反向传播调整相关系数,调整3D模型,不断进行迭代。每次参与计算的是一个三角晶格,如果人脸被遮挡,则该部分不参与损失计算。

    (c) 具体迭代时采用由粗到精的方式,初始的时候使用低分辨率的图像,只优化第一个主成分的系数,后面再逐步增加主成分。在后续一些迭代步骤中固定外部参数,对人脸的各个部位分别优化。

    对于只需要获取人脸形状模型的应用来说,很多方法都会使用2D人脸关键点来估计出形状系数,具有更小的计算量,迭代也更加简单,另外还会增加一个正则项,所以一个典型的优化目标是如下:

    对于Model fitting问题来说,除了模型本身的有效性,还有很多难点。

    (1) 该问题是一个病态问题,本身并没有全局解,容易陷入不好的局部解。

    (2) 人脸的背景干扰以及遮挡会影响精度,而且误差函数本身不连续。

    (3) 对初始条件敏感,比如基于关键点进行优化时,如果关键点精度较差,重建的模型精度也会受到很大影响。

    2.3 3DMM模型的发展

    要使用3DMM模型来完成人脸重建,首先就需要一个数据库来建立人脸基向量空间,Blanz等人在1999年的文章[1]中提出了采集方法,但是没有开源数据集,Pascal Paysan等人在2009年使用激光扫描仪精确采集了200个人脸数据得到了Basel Face Model数据集[2](简称BFM模型),基本信息如下:

    (1)采用ABW-3D结构光系统进行采集,采集时间约1s,相比于激光平均15s的采集方案更加具有优势。整个数据集包含200张三维的人脸,其中100张男性,100张女性,大部分为高加索人脸。年龄分布8~62岁,平均年龄24.97岁,体重40~123千克,平均66.48千克。每一个人都被采集3次中性表情,并选择其中最自然的一次。

    (2)在对采集后的点进行处理的过程中,模型的每一个点的位置都进行了精确匹配,也就是说每一个点都有实际的物理意义,比如属于右嘴角等。经过处理后,每一个模型由53490个点描述。

    该数据库的平均人脸形状和平均人脸纹理如下:

    Basel Face Model数据集只有200个人,而近期研究者基于此模型采集了9663个人得到LSFM模型[3],能够进一步提升表达能力。

    2009年发布的Basel Face Model版本中没有表情系数,而2017年发布的版本BFM 2017[4]中提供了表情系数,同样还是一个线性模型。

    当然了,在国内也有一个著名的数据集,就是FaceWarehouse[5],不过不开源,一般研究者拿不到数据。

    当然也有一些商业号称会开源更好的模型,这个大家可以拭目以待。人脸的三维模型数据之所以不公开,是因为使用高精度的三维模型可以很容易仿真真实人脸,容易发生安全事故。

    当前基于3DMM的表情模型主要有两个思路,分别是加性模型和乘性模型。加性模型就是线性模型了,将表情作为形状的一个偏移量,Es,Ee分别表示形状和表情基,Ws,We分别表示对应的系数。

    但是因为表情也会改变人脸的形状,因此它和形状并非完全正交的关系,所以有的研究者提出了乘性模型,如下。

    其中de是一个表情迁移操作集合,第j个操作即为Tj,δ都是校准向量

    另一方面,纹理模型也被称为表观模型,它相对于形状模型来说更加复杂,受到反射率和光照的影响,不过大部分的3DMM模型不区分两者,所以我们将其视为一个因素,即反射率。

    光照模型通常采用的是球面模型,光照模型比较复杂,我们这里就不列出具体的表达式,大家可以自行阅读相关论文。

    在2009年提出的BFM模型中,纹理模型是一个线性模型,即由多个纹理表情基进行线性组合。后续的研究者们在整个基础上增加了纹理细节,用于仿真脸部的皱纹等。

    尽管在大多数情况下,我们使用的都是线性3DMM模型,但是非线性3DMM模型同样也被研究[6],由于不是主流,就不展开讲了。

    [1] Blanz V, Vetter T. A morphable model for the synthesis of 3D faces[C]. international conference on computer graphics and interactive techniques, 1999: 187-194.

    [2] Booth J, Roussos A, Ponniah A, et al. Large Scale 3D Morphable Models[J]. International Journal of Computer Vision, 2018, 126(2): 233-254.

    [3] Paysan P, Knothe R, Amberg B, et al. A 3D Face Model for Pose and Illumination Invariant Face Recognition[C]. advanced video and signal based surveillance, 2009: 296-301.

    [4] Gerig T , Morel-Forster A , Blumer C , et al. Morphable Face Models - An Open Framework[J]. 2017.

    [5] Cao C, Weng Y, Zhou S, et al. FaceWarehouse: A 3D Facial Expression Database for Visual Computing[J]. IEEE Transactions on Visualization and Computer Graphics, 2014, 20(3): 413-425.

    [6] Tran L, Liu X. Nonlinear 3D Face Morphable Model[C]. computer vision and pattern recognition, 2018: 7346-7355.

    3. 深度学习3DMM重建

    传统的3DMM及其求解核心思路我们上面已经讲述了,接下来要重点说的是基于深度学习的3DMM重建及其研究进展。

    3.1 全监督方法

    前面给大家介绍了3DMM模型,传统的方法需要去优化求解相关系数,基于深度学习的方法可以使用模型直接回归相关系数,以Regressing Robust and Discriminative 3D Morphable Models with a very Deep Neural Network中提出的3DMM CNN[7]方法为代表。

    3DMM CNN是一个非常简单的回归模型,它使用了ResNet101网络直接回归出3DMM的形状系数和纹理系数,形状系数和纹理系数各有99维,除此之外还有几个核心问题。

    (1) 首先是数据集的获取。由于真实的三维人脸和二维人脸图像对非常缺乏,采集成本高,作者们用CASIA WebFace数据集中的多张照片进行model fitting求解生成了对应的三维人脸模型,将其作为真值(Ground Truth),从而得到了二维三维图像对。

    (2) 然后是优化目标的设计。因为重建的结果是一个三维模型,所以损失函数是在三维的空间中计算,如果使用标准的欧拉损失函数来最小化距离,会使得到的人脸模型太泛化,趋于平均脸。对此作者们提出了一个非对称欧拉损失,使模型学习到更多的细节特征,使三维人脸模型具有更多的区别性,公式如下:

    γ是标签,γp是预测值,通过两个权重λ1和λ2对损失进行控制,作者设定λ2权重更大,所以是期望γp能够更大一些,从而提供更多的细节。

    除了预测形状系数外,3DMM的研究者们还提出了ExpNet[8]预测表情系数,FacePoseNet[9]预测姿态系数,验证了基于数据和CNN模型学习出相关系数的可行性。

    真实数据集的获取是比较困难的,而且成本高昂,导致数据集较小,所以基于真实数据集训练出来的模型鲁棒性有待提升。很多的方法使用了仿真的数据集,可以产生更多的数据进行学习,但是仿真的数据集毕竟与真实的数据集分布有差异,以及头发等部位缺失,导致模型泛化到真实数据集的能力较差。

    3.2 自监督方法

    三维人脸重建中真实的数据集获取成本非常高,研究者往往基于少量数据或者仿真数据进行研究,所训练出来的模型泛化能力会受到限制,自监督的方法则是一个解决该问题的重要思路。这一类方法不依赖于真实的成对数据集,它将二维图像重建到三维,再反投影回二维图,这一类方法以MoFa[10]为代表,整个流程如下图所示:

    在上图中,输入首先经过一个Deep Encoder提取到语义相关的系数,系数包含了人脸姿态,形状,表情,皮肤,场景光照等信息。然后将该系数输入基于模型的decoder,实现三维模型到二维图像的投影,模型可以使用3DMM模型。最后的损失是基于重建的图像和输入图像的像素损失。当然,还可以添加关键点损失,系数正则化损失作为约束。

    3.3 人脸的三维特征编码

    通常的3DMM模型预测3DMM的系数,这没有充分发挥出CNN模型对于像素的回归能力,如果我们基于3DMM模型将三维人脸进行更好的特征编码,预期可以获得更好的结果。

    这里我们介绍一下两个典型代表[11][12],其一是3DDFA,它使用Projected Normalized Coordinate Code(简称PNCC)作为预测特征。

    一个三维点包括X,Y,Z和R,G,B值,将其归一化到0~1之后便称之为Normalized Coordinate Code。如果使用3DMM模型将图像往X-Y平面进行投影,并使用Z-Buffer算法进行渲染,NCC作为Z-buffer算法的color-map,便可以得到PNCC图。

    论文《Face Alignment Across Large Poses: A 3D Solution》基于此提出了3DDFA框架,输入为100×100的RGB图和PNCC(Projected Normalized Coordinate Code)特征图,两者进行通道拼接。算法的输出为更新后的PNCC系数,包括6维姿态,199维形状和29维表情系数。

    整个网络如下:包含4个卷积层,3个pooling层和2个全连接层,前两个卷积层局部共享,后两个卷积层不采用局部共享机制。这是一个级连迭代的框架,输入为第k次更新的PNCC特征,更新它的误差,损失使用L1距离。

    由于不同维度的系数有不同的重要性,作者对损失函数做了精心的设计,通过引入权重,让网络优先拟合重要的形状参数,包括尺度、旋转和平移。当人脸形状接近真值时,再拟合其他形状参数,实验证明这样的设计可以提升定位模型的精度。

    由于参数化形状模型会限制人脸形状变形的能力,作者在使用3DDFA拟合之后,抽取HOG特征作为输入,使用线性回归来进一步提升2D特征点的定位精度。

    其二是PRNet[12],论文Joint 3D Face Reconstruction and Dense Alignment with Position Map Regression Network中提出了PRNet(Position map Regression Network),它利用UV位置图(UV position map)来描述3D形状。

    在BFM模型中,3D顶点数为53490个,作者选择了一个大小为256×256×3的图片来进行编码,其中像素数目等于256×256=65536,大于且接近53490。这个图被称为UV位置图(UV position map),它有三个通道,分别是X,Y,Z,记录了三维位置信息。值得注意的是,每个3D的顶点映射到这张UV位置映射图上都是没有重叠的。

    有了上面的表示方法,就可以用CNN网络直接预测UV位置图,采用一个编解码结构即可。

    另外,作者们为了更好的预测坐标,或者说为了使预测出来的结果更有意义,计算损失函数时对不同区域的顶点误差进行加权。不同区域包括特征点,鼻子眼睛嘴巴区域,人脸其他部分,脖子共四个区域。它们的权重比例为16:4:3:0,可见特征点最重要,脖子不参与计算。

    [7] Tran A T, Hassner T, Masi I, et al. Regressing robust and discriminative 3D morphable models with a very deep neural network[C]//Computer Vision and Pattern Recognition (CVPR), 2017 IEEE Conference on. IEEE, 2017: 1493-1502.

    [8] Chang F J, Tran A T, Hassner T, et al. ExpNet: Landmark-free, deep, 3D facial expressions[C]//2018 13th IEEE International Conference on Automatic Face & Gesture Recognition (FG 2018). IEEE, 2018: 122-129.

    [9] Chang F J, Tuan Tran A, Hassner T, et al. Faceposenet: Making a case for landmark-free face alignment[C]//Proceedings of the IEEE International Conference on Computer Vision. 2017: 1599-1608.

    [10] Tewari A, Zollhöfer M, Kim H, et al. Mofa: Model-based deep convolutional face autoencoder for unsupervised monocular reconstruction[C]//The IEEE International Conference on Computer Vision (ICCV). 2017, 2(3): 5.

    [11] Zhu X, Lei Z, Liu X, et al. Face alignment across large poses: A 3d solution[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2016: 146-155.

    [12] Feng Y, Wu F, Shao X, et al. Joint 3D Face Reconstruction and Dense Alignment with Position Map Regression Network[J]. arXiv preprint arXiv:1803.07835, 2018.

    4. 难点和展望

    从1999年被提出,至今3DMM模型已经有超过20年的历史,技术已经发展到从早期基于传统的优化方法到如今基于深度学习模型的系数回归,不过当前的3DMM模型还面临着许多的挑战。

    (1) 当前的模型基本上都受限于人脸,没有眼睛,嘴唇以及头发信息,然而这些信息对于很多的应用却非常有效。

    (2) 3DMM模型参数空间是一个比较低维的参数空间,并且纹理模型过于简单。基于3DMM模型的方法面临的最大问题就是结果过于平均,难以重建人脸皱纹等细节特征,并且无法恢复遮挡。对此有的方法通过增加局部模型[13]进行了改进,而最新的生成对抗网络技术[14]也开始被用于纹理建模。

    (3) 遮挡脸的信息恢复。二维的人脸信息一旦被遮挡,也难以被精确地重建,除了利用人脸的对称先验信息进行补全外,有的方法借鉴了检索匹配[15]的思路,即建立一个无遮挡的数据集,将重建的模型进行姿态匹配和人脸识别相似度匹配,然后经过2D对齐,使用基于梯度的方法来进行纹理迁移,也有的方法使用GAN来进行遮挡信息恢复[16]。

    (3) 当前3DMM模型中主要使用PCA来提取主成分信息,但是这不符合我们通常对人脸的描述,因此这并非是一个最合适的特征空间。

    (4) 当前存在着各种各样的3DMM模型的变种,但是没有一个模型能够在各种场景下取得最优的效果。

    另一方面,3DMM模型也与许多新的技术开始结合,比如与生成对抗网络模型一起进行人脸的数据增强[17],姿态编辑[17],人脸的特征恢复[18],对于提升人脸识别模型在具有挑战性的大姿态以及遮挡场景下的性能中具有非常重要的意义。

    [13] Richardson E, Sela M, Or-El R, et al. Learning detailed face reconstruction from a single image[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2017: 1259-1268.

    [14] Sela M, Richardson E, Kimmel R, et al. Unrestricted Facial Geometry Reconstruction Using Image-to-Image Translation[C]. international conference on computer vision, 2017: 1585-1594.

    [15] Tran A T, Hassner T, Masi I, et al. Extreme 3D Face Reconstruction: Seeing Through Occlusions[C]//CVPR. 2018: 3935-3944.

    [15] Egger B, Smith W A, Tewari A, et al. 3D Morphable Face Models - Past, Present and Future[J]. arXiv: Computer Vision and Pattern Recognition, 2019.

    [16] Zhao J, Xiong L, Jayashree P K, et al. Dual-Agent GANs for Photorealistic and Identity Preserving Profile Face Synthesis[C]. neural information processing systems, 2017: 66-76.

    [17] Yin X, Yu X, Sohn K, et al. Towards Large-Pose Face Frontalization in the Wild[C]. international conference on computer vision, 2017: 4010-4019.

    [18] Yuan X, Park I. Face De-Occlusion Using 3D Morphable Model and Generative Adversarial Network[C]. international conference on computer vision, 2019: 10062-10071.

    展开全文
  • B型超声设备图像处理:用二维数据生成扇形图像

    千次阅读 多人点赞 2020-03-05 15:57:59
    面对这个问题,我也懵逼,什么是扇扫?矩形数据又啥?细问之下,才知道这B型超声设备的数据处理问题。B超输出的数据保存在一个二维数组中,但显示在屏幕上的却需要转换为扇形。稍微思考一下,应该不难解决...

    昨晚,有同学私信咨询:如何将矩形数据转为扇扫图像?面对这个问题,我也是一脸懵逼,什么是扇扫?矩形数据又是啥?细问之下,才知道这是B型超声设备的数据处理问题。B超输出的数据保存在一个二维数组中,但显示在屏幕上的却需要转换为扇形。如下图所示:
    在这里插入图片描述
    稍微思考一下,应该不难解决。比较直接的方法是,将二维数组的每一列旋转合适的角度,就可以拼成一副图像。我们用参数angle表示扇形夹角的一半,用参数k表示输出图像的高度与每一列数据数量的比值,很容易写出如下代码:

    # -*- coding:utf-8 -*-
    
    import numpy as np
    from PIL import Image
    
    def square2fan(fn_squ, fn_fan, angle=45, k=1):
        """将矩形图像转为扇形
        
        fn_squ      - 输入文件名
        fn_fan      - 输出文件名
        angle       - 扇形夹角度数
        k           - 扇形因子,k大于1输出环形
        """
        
        im = Image.open(fn_squ) # 打开输入图像为PIL对象
        mode = im.mode # 输入图像模式
        w, h = im.size # 输入图像分辨率
        rows, cols = int(np.ceil(h*k)), int(np.ceil(2*h*k*np.sin(np.radians(angle)))) # 输出图像高度和宽度
        cols += cols%2 # 宽度为单数则加1
        
        im_squ = np.array(im) # 输入图像转为numpy数组
        im_fan = np.zeros((rows, cols, im_squ.shape[2]), dtype=np.uint8) # 生成输出图像的numpy数组(全透明)
        
        alpha = np.radians(np.linspace(-angle, angle, w)) # 生成扇形角度序列,长度与输入图像宽度一致
        for i in range(w): # 遍历输入图像的每一列
            # 当前列各像素在输出图像上的行号
            d = np.cos(alpha[i])*rows
            lats = np.int_(np.linspace(d*(k-1)/k, d, h)).astype(np.int)
            
            # 当前列各像素在输出图像上的列号
            d = np.sin(alpha[i])*rows
            lons = np.int_(np.linspace(cols/2+d*(k-1)/k, cols/2+d, h)).astype(np.int)
            
            # 输出图像上对应的点替换为输入图像的点
            im_fan[(lats, lons)] = im_squ[:,i]
        
        # 保存为文件
        im = Image.fromarray(im_fan, mode=im.mode)
        im.save(fn_fan)
        
    if __name__ == '__main__':
        square2fan('demo.png', 'out.png', angle=45, k=1.0)
    

    然而,输出图像的效果却不够完美:图像下部出现了镂空的白点。
    在这里插入图片描述
    没关系,我们再加上一个临近点插值,并用matplotlib将输入数据和输出数据画在一起。完整代码如下:

    # -*- coding:utf-8 -*-
    
    import numpy as np
    from PIL import Image
    import matplotlib.pyplot as plt
    
    def square2fan(fn_squ, fn_fan, angle=45, k=1):
        """将矩形图像转为扇形
        
        fn_squ      - 输入文件名
        fn_fan      - 输出文件名
        angle       - 扇形夹角度数
        k           - 扇形因子,k大于1输出环形
        """
        
        im = Image.open(fn_squ) # 打开输入图像为PIL对象
        mode = im.mode # 输入图像模式
        w, h = im.size # 输入图像分辨率
        rows, cols = int(np.ceil(h*k)), int(np.ceil(2*h*k*np.sin(np.radians(angle)))) # 输出图像高度和宽度
        cols += cols%2 # 宽度为单数则加1
        
        im_squ = np.array(im) # 输入图像转为numpy数组
        im_fan = np.zeros((rows, cols, im_squ.shape[2]), dtype=np.uint8) # 生成输出图像的numpy数组(全透明)
        
        alpha = np.radians(np.linspace(-angle, angle, w)) # 生成扇形角度序列,长度与输入图像宽度一致
        for i in range(w): # 遍历输入图像的每一列
            # 当前列各像素在输出图像上的行号
            d = np.cos(alpha[i])*rows
            lats = np.int_(np.linspace(d*(k-1)/k, d, h)).astype(np.int)
            
            # 当前列各像素在输出图像上的列号
            d = np.sin(alpha[i])*rows
            lons = np.int_(np.linspace(cols/2+d*(k-1)/k, cols/2+d, h)).astype(np.int)
            
            # 输出图像上对应的点替换为输入图像的点
            im_fan[(lats, lons)] = im_squ[:,i]
        
        # 空白区域临近点插值
        for row in range(int(rows*(k-1)/k)+1, rows):
            ps, pe = 0, 0
            for col in range(cols):
                if im_fan[row, col, 3] > 0:
                    if ps == 0:
                        ps, pe = col, col
                    else:
                        pe = col
            for col in range(ps-1 ,pe):
                if im_fan[row, col, 3] == 0:
                    im_fan[row, col] = im_fan[row, col-1]
        
        # 绘图
        plt.figure('B超扇扫', facecolor='#f4f4f4', figsize=(15, 7))
        plt.subplot(121)
        plt.imshow(im_squ)
        plt.subplot(122)
        plt.imshow(im_fan)
        plt.savefig('out_plt.png')
        plt.show()
        
        # 保存为文件
        im = Image.fromarray(im_fan, mode=im.mode)
        im.save(fn_fan)
        
    if __name__ == '__main__':
        square2fan('demo.png', 'out.png', angle=35, k=1.2)
    

    最终效果如下:
    在这里插入图片描述

    展开全文
  • 本文作者:小嗷 微信公众号:aoxiaoji 吹比QQ群:736854977 ... ...漫水填充(Flood fill),也称为种子填充(seed fill),一种确定多维数组...(灰度图是二维,彩色图三维) 灰度图的二维:一般来说,一维高(...

    本文作者:小嗷

    微信公众号:aoxiaoji

    吹比QQ群:736854977

    简书链接:https://www.jianshu.com/u/45da1fbce7d0


    1.前言

    1.前言内容

    2.简介及用途

    简介:

    漫水填充(Flood fill),也称为种子填充(seed fill),是一种确定多维数组中连接到给定节点的区域的算法。(灰度图是二维,彩色图是三维)

    灰度图的二维:一般来说,一维是高(行),一维是宽(列)。

    即:char a[3][4] = 246;

    a为3*4(3行4列)的像素值为:246

    彩色图:多了一维是图像深度


    本文你会找到以下问题的答案:

    1. 漫水填充算法
    2. 灰度图是二维,彩色图是三维
    3. 堆栈是什么?
    4. for (;;)等于while(1)
    5. (A?B:C)

    A?B:C 这个运算是判断A的真假,若是真就执行B如是假就执行C


    2.1 漫水填充算法包含三个参数(自己写算法的话):

    开始节点、目标颜色和替换颜色。

    该算法查找数组中的所有节点,这些节点通过目标颜色的路径连接到起始节点,并将它们更改为替换颜色。有许多方法可以构造漫水填充算法,但它们都使用队列或堆栈数据结构,显式或隐式地。

    根据我们是否考虑连接在角落的节点,我们有两种变化:8路和4路。(即:核是3*3的正方形,还是自定义十字形),取其中一个4路在演示。

    一个隐式堆栈的(递归)漫水填充实现(对于一个二维数组)如下所示:

    基于堆栈的递归实现(四【十字形】)的思路

    1. 首先。如果目标颜色等于替换颜色。
    2. 如果节点的颜色不等于目标颜色,不处理。
    3. 将节点的颜色设置为替换颜色。
    4. 执行漫水填充(向节点南部的一步,目标颜色,替换颜色)。
    5. 执行漫水填充(向节点以北一步,目标颜色,替换颜色)。
    6. 执行漫水填充(在节点的西面一步,目标颜色,替换颜色)。
    7. 执行漫水填充(在节点的东边一步,目标颜色,替换颜色)。
    8. 处理完数组,退出

    效果如下:(十字形)

    2.4路

    即:东、南、西、北、

    8路的效果如下:(核是3*3的正方形)

    2.8路

    即:东、南、西、北、东南、西南、西北、东北、 8个方向

    用给定的颜色填充连接的组件。

    2.2 堆栈

    在计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构。

    堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

    要点:堆,队列优先,先进先出(FIFO—first in first out) [1] 。栈,先进后出(FILO—First-In/Last-Out)。

    堆:

    8.堆

    堆栈空间分配

    栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

    堆栈缓存方式

    栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。

    堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

    11.3API函数

     cv::floodFill  (   InputOutputArray    image,
            InputOutputArray    mask,
            Point   seedPoint,
            Scalar      newVal,
            Rect *      rect = 0,
            Scalar      loDiff = Scalar(),
            Scalar      upDiff = Scalar(),
            int     flags = 4 
        )   
    

    参数详解:

    • image:InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。

    • mask:InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。

    • seedPoint:Point类型的seedPoint,漫水填充算法的起始点。
    • newVal:Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
    • rect:Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
    • loDiff:Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。
    • upDiff:Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
    • flags:int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。

    低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。

    (请看上面第二部分的东南西北的解释,哈哈哈)

    高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:

    • FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
    • FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。

    中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充

    而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:

    flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
    

    接着就是理论运用:

    功能cv::floodFill从种子点开始,以指定的颜色填充连接的组件。连接性是由相邻像素的颜色/亮度接近决定的。在(x,y)处的像素,如果:

    • 在灰度图像和浮动范围的情况下:

    4.公式

    • 在灰度图像和固定范围的情况下:

    5.公式

    • 在彩色图像和浮动范围的情况下:

    6.公式

    • 在彩色图像和固定范围的情况下:

    7.公式

    src(x′,y′)的值是一个已知的像素邻居属于组件。也就是说,要添加到所连接的组件中,像素的颜色/亮度应该足够接近:

    在浮动范围内,它的一个邻居的颜色/亮度已经属于连接组件。
    
    在固定范围内,种子点的颜色/亮度。
    

    使用这些函数可以将连接的组件标记为指定的颜色,或者构建一个掩膜,然后提取轮廓,或者将区域复制到另一个图像,等等。

    简单来说公式(灰度图为例):

    • 固定范围

    5.公式

    (比如起点的像素点是200,loDiff是20,upDiff是50)

    像素点必须要要在以下范围内,才可以上色

    即:180《像素点《250

    • 浮动范围

    4.公式

    (比如临近的像素点是200【已知的像素邻居】,loDiff是20,upDiff是50)

    像素点必须要要在以下范围内,才可以上色

    即:已知的像素邻居的值 - 20 《像素点《 已知的像素邻居的值 - 50

    代码如下:

    //videoio:视频流的输入和输入
    //imgcodecs:用于图像文件的载入(imread)和输出(imwrite)
    //imgproc:图像处理模块
    //highgui:高层图形用户界面(GUI),包括媒体输入输出、视频捕捉、图像交互界面接口、图像和视频的编码解码等
    #include "opencv2/imgproc.hpp"
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/videoio.hpp"
    #include "opencv2/highgui.hpp"
    #include <iostream>
    using namespace cv;
    using namespace std;
    static void help()
    {
        cout << "\nThis program demonstrated the floodFill() function\n"
                "Call:\n"
                "./ffilldemo [image_name -- Default: ../data/fruits.jpg]\n" << endl;
        cout << "Hot keys: \n"
                "\tESC - quit the program\n"
                "\tc - switch color/grayscale mode\n"
                "\tm - switch mask mode\n"
                "\tr - restore the original image\n"
                "\ts - use null-range floodfill\n"
                "\tf - use gradient floodfill with fixed(absolute) range\n"
                "\tg - use gradient floodfill with floating(relative) range\n"
                "\t4 - use 4-connectivity mode\n"
                "\t8 - use 8-connectivity mode\n" << endl;
    }
    Mat image0, image, gray, mask;
    int ffillMode = 1;
    int loDiff = 20, upDiff = 20;
    int connectivity = 4;
    int isColor = true;
    bool useMask = false;
    int newMaskVal = 255;
    static void onMouse( int event, int x, int y, int, void* )
    {
        if( event != EVENT_LBUTTONDOWN )
            return;
        Point seed = Point(x,y);
        int lo = ffillMode == 0 ? 0 : loDiff;
        int up = ffillMode == 0 ? 0 : upDiff;
        int flags = connectivity + (newMaskVal << 8) +
                    (ffillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
        int b = (unsigned)theRNG() & 255;
        int g = (unsigned)theRNG() & 255;
        int r = (unsigned)theRNG() & 255;
        Rect ccomp;
        Scalar newVal = isColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);
        Mat dst = isColor ? image : gray;
        int area;
        if( useMask )
        {
            threshold(mask, mask, 1, 128, THRESH_BINARY);
            area = floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo),
                      Scalar(up, up, up), flags);
            imshow( "mask", mask );
        }
        else
        {
            area = floodFill(dst, seed, newVal, &ccomp, Scalar(lo, lo, lo),
                      Scalar(up, up, up), flags);
        }
        imshow("image", dst);
        cout << area << " pixels were repainted\n";
    }
    int main( int argc, char** argv )
    {
        cv::CommandLineParser parser (argc, argv,
            "{help h | | show help message}{@image|../data/fruits.jpg| input image}"
        );
        if (parser.has("help"))
        {
            parser.printMessage();
            return 0;
        }
        string filename = parser.get<string>("@image");
        image0 = imread(filename, 1);
        if( image0.empty() )
        {
            cout << "Image empty\n";
            parser.printMessage();
            return 0;
        }
        help();
        image0.copyTo(image);
        cvtColor(image0, gray, COLOR_BGR2GRAY);
        mask.create(image0.rows+2, image0.cols+2, CV_8UC1);
        namedWindow( "image", 0 );
        createTrackbar( "lo_diff", "image", &loDiff, 255, 0 );
        createTrackbar( "up_diff", "image", &upDiff, 255, 0 );
        setMouseCallback( "image", onMouse, 0 );
        for(;;)
        {
            imshow("image", isColor ? image : gray);
            char c = (char)waitKey(0);
            if( c == 27 )
            {
                cout << "Exiting ...\n";
                break;
            }
            switch( c )
            {
            case 'c':
                if( isColor )
                {
                    cout << "Grayscale mode is set\n";
                    cvtColor(image0, gray, COLOR_BGR2GRAY);
                    mask = Scalar::all(0);
                    isColor = false;
                }
                else
                {
                    cout << "Color mode is set\n";
                    image0.copyTo(image);
                    mask = Scalar::all(0);
                    isColor = true;
                }
                break;
            case 'm':
                if( useMask )
                {
                    destroyWindow( "mask" );
                    useMask = false;
                }
                else
                {
                    namedWindow( "mask", 0 );
                    mask = Scalar::all(0);
                    imshow("mask", mask);
                    useMask = true;
                }
                break;
            case 'r':
                cout << "Original image is restored\n";
                image0.copyTo(image);
                cvtColor(image, gray, COLOR_BGR2GRAY);
                mask = Scalar::all(0);
                break;
            case 's':
                cout << "Simple floodfill mode is set\n";
                ffillMode = 0;
                break;
            case 'f':
                cout << "Fixed Range floodfill mode is set\n";
                ffillMode = 1;
                break;
            case 'g':
                cout << "Gradient (floating range) floodfill mode is set\n";
                ffillMode = 2;
                break;
            case '4':
                cout << "4-connectivity mode is set\n";
                connectivity = 4;
                break;
            case '8':
                cout << "8-connectivity mode is set\n";
                connectivity = 8;
                break;
            }
        }
        return 0;
    }
    

    原图:

    效果图:

    效果图

    14.png

    1. 本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)
    2. 大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年
    3. 写文章主要是为了后人少走点弯路,多交点朋友,一起学习
    4. 如果有好的图像识别群拉我进去QQ:631821577
    5. 就我一个白板,最后还是成的,你们别怕,慢慢来把

    7.二维码

    分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。

    • 邮箱:631821577@qq.com
    • QQ群:736854977
    • 有什么疑问公众号提问,下班或者周六日回答,ths

    推荐文章:

    21.失真/低高通/振铃效应/旁瓣泄漏效应/频域滤波/图像深度/频带/线性滤波源码分析(数学篇) - OpenCV从零开始到图像

    感言

    很多知识点都是串起来,怎么说呢?可以理解之前二十几篇文章的知识布局,才理解这篇或者做到这篇文章。

    实在有点不好意思,小嗷因为个人原因(工资低老想着跳槽,哈哈哈)中断了一期。一周5篇实在吃不消。

    还有1开始几篇文章布局什么真难看,小嗷会重新群发(微信规矩:不群发,上不了链接地址)

    代码地址:

    链接:

    https://pan.baidu.com/s/1RESLgnXlwZ74E-Eh1yRaXQ

    密码:nq8r

    展开全文
  • 学习链表的时候一懵逼,发现需要从二维数组与指针开始补习,从零开始的代码学习笔记第一篇,希望今后也能坚持下去。 ————————————————...一个没什么特别的二维数组,我的学习目的弄清楚a[0],a[2

    学习链表的时候一脸懵逼,发现需要从二维数组与指针开始补习,从零开始的代码学习笔记第一篇,希望今后也能坚持下去。

    ——————————————————————————————————

     

    一、辨析数组形式的含义

    定义3行4列的二维数组a如下:

    int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};

    一个没什么特别的二维数组,我的学习目的是弄清楚a[0],a[2],a[3]对应一维数组的首地址的含义和指针基类型,分清不同表示形式的含义。


    这个二维数组由三个一维数组组成,根据C标准的规定,a是指向第一个元素的指针,a=&a[0],所以:

    a+i=&a[i]

    a[i]=*(a+i)


    a[0],a[1],a[3]是一维数组名,表示对应一维数组的首地址的指针,如a[0]是一维数组a[0][0],a[0][1],a[0][2],a[0][3]的首地址,其指针基类型为int型,所以以下等价成立:

    a[0]=&a[0][0]

    a[0]+1=&a[0][1]

    a[1]+1=&a[1][1]

    a[1]+2=&a[1][2]

     

    分析可知,a向后移动1个储存单元,相当于移动了一个一维数组所有元素的存储位置,这个一维数组的元素个数就是数组a的列下表,因此二维数组名作为指针,其基类型是一维数组。对a而言其基类型是具有4个整型元素的一维数组。如果a数组的首地址是2000,a+1的值就是2000+4×sizeof(int)

     

    总结:

    a+i=&a[i]

    a[i]+j=&a[i][j]

    a[i][j]=* (&a[i][j])=* (a[i]+j)=* (*(&a[i])+j)=* (* (a+i)+j)

     

    【二维数组a代表的不同含义】

    a:二维数组首地址,第1行的首地址,基类型是一维数组的指针

    a+i,&a[i]:第i+1行的首地址,基类型是一维数组的指针

    *(a+i)+j,a[i]+j,&a[i][j]:第i+1行第j+1列的元素的地址,基类型是整型的指针

    * (* (a+i)+j),a[i][j]:第i+1行第j+1 列的元素

     

    其中a,a+1,&a[i]这些指向一维数组的指针习惯上称为行指针,而诸如a[i],a[i]+j,* (a+i),&a[i][j] 都是基类型与元素类型一致的指针,习惯上称为列指针。

    两者可以互相转换,行指针通过加*运算变成列指针,列指针加&运算变成行指针。

     

    二、指针与二维数组

    int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
    
    int *p
    
    p=a; /*错误*/
    
    p=a[i]; /*正确*/
    
    p=*(a+1); /*正确*/
    
    p=*(a+1)+2; /*正确*/

    错误原因是基类型不匹配。

     

    C语言提供了指向一维数组的指针(基类型为一维数组),定义格式为:

    类型说明符(*指针变量名)长度;

    注:①括号不能省略

      ②长度等于一维数组元素的个数;

    int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
    
    int (*p)[4]; /*4与上面列的数值4相同*/
    
    p=a; /*正确*/

    用指针变量来取代a,该指针的基类型必须是具有4个元素的一维数组(因为数组a的列下标为4)


    另外,二维数组元素的引用也可以用指针数组实现

    int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
    
    int *p[3]={a[0],a[1],a[2]}; /*3与上面行的数值3相同,p[i]指向a[i]*/
    
    int i,j;
    
    for(i=0;i<3;i++){
    
       for(j=0;j<4;j++){
    
           printf("%d",*(*(p+i)+j));
    
       printf("\n");
    
    }


    三、指针与函数

    1、指针作为实际参数

    int fun(int *p){
    
        ……
    
    }
    


    2、返回指针值的指针型函数

    int *fun(int x){
    
        ……
    
    }

    表示fun是一个返回指针值的指针型函数,它返回的指针指向一个整型变量

    指针型函数返回的地址可以是实参中的指针,也可能是返回指向外部变量或指向声明为static的局部变量的指针,但是记住不要范围指向自动局部变量的指针,如下代码是错误的:

    int *fun(void){
    
       int i;
    
       ……
    
       return &i;
    
    }

    因为一旦fun返回,变量i就不存在了,指向它的指针无效


    3、函数指针变量

    在C语言中,一个函数总是占用一段连续的内存区,而函数名就素该函数所占内存区的首地址,我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使改指针变量指向该函数,然后通过指针变量就可以找到并调用这个函数

    (1)定义

    类型说明符(*指针变量名)();

    其中类型说明符表示被指函数的返回值的类型,空括号表示指针变量指向一个函数

    (2)赋值

    函数指针变量=函数名;

    (3)调用

    函数指针变量名(实参列表);或(*函数指针变量名)(实参列表);



    四、总结

    int *p:p为指向整型数据的指针变量

    int **p:p为一个指向整型指针的指针变量

     

    int *p[n]:p为指针数组,由n个指向整型数据的指针元素组成,n与原二维数组的行数相同

    int (*p)[n]:p为指向含n个元素的一维数组的指针变量,n与原二维数组的列数相同

     

    int *p(int i):p为返回整型指针的函数

    int (*p)():p为指向返回整型值的函数指针

     

    ——————————————————————————————————

    感觉搞清楚了很多东西,然而有些地方还是分不太清楚,可能需要在练习中巩固吧。开心,可以愉♂快地去学习链表了(不不是的

     

    参考资料:《C语言程序设计》科学出版社


    展开全文
  • 什么是人脸识别?

    2020-09-14 18:12:56
    早期的面部识别算法(今天仍以改进且更加自动化的形式使用)依靠生物特征识别技术(例如眼睛之间的距离)将所测量的面部特征从二维图像转换为一组数字(一种特征向量或模板)来描述面部。然后,识别过程将这些向量与...
  • numpy是什么: 功能强大的N数组对象。 精密广播功能函数。 集成 C/C+和Fortran 代码的工具。 强大的线性代数、傅立叶变换和随机数功能 numpy利器之一:Ndarray numpy利器之:索引和切片 numpy和matlab...
  • 克莱因瓶一个密闭的空间体,对比的对象球体,而不应该玻璃杯。 玻璃杯有杯口,内部和外部的空间从杯口的切面处分割开来。 杯口如果不加盖子,则内外部空间联通的。...二维平面中,左侧如何变成右侧
  • 一、我们目前为止拥有什么 为了有一个连续完整的认识,在介绍最后一节前,先梳理下至今我们训练了哪些数据特征,并且训练它们的目的是什么。 1.ft_data:利用手工标注工具,获取最原始的样本训练...二维坐标集合p...
  • 在动漫中,角色的面部表情可以说非常丰富,比如...注意不严格地顺着弧度安排也没关系,只需上下压缩,在二维平面上就已经显得像这么回事了。 1、如下图: ①下巴凸出至圆外。 ②沿着轮廓的弧度安排脸部组成部分...
  • 在第部分中,我使用主成分分析来找到面部图像的低表示。 此代码已在octave版本4.2.1上成功实现 要开始该项目,您将需要下载代码并将其内容解压缩到您希望运行该项目的目录中。 如果需要,在开始本练习之前,请...
  • 文章目录选择动机解决什么问题方法重建质量评估图像距离Mahalanobis ...从二维图像中重建三维人脸,会因为姿势、光线、遮挡、表情等情况带来一定的难度。这篇论文的出发点:从一个人的多张人脸图像中重建.
  •  这种题一个非常重要的辅助手段就是前缀和——对于矩形问题,是二维前缀和(虽然说相信NOIP提高组能拿300分以上的同学都知道二维前缀和是什么……);但是此题要预处理4个方向的前缀和,这确实不太多见。总之先预处
  • 2016-10-7

    2016-10-08 00:01:43
    完成情况:一维 60 二维 40 具体情况: 这个数组是真的难,看了一遍视频是一懵逼,于是又观察了几遍代码,看了看书,慢慢也能理解其中的一些 东西了,不过还是有点似懂非懂,现在还是不太明白数组的作用是什么,...
  • 想要实现简单的人头...建立相机成像几何模型:相机把三维现实场景转换为二维图像的过程损失了很多信息, 因此该过程的函数不可逆的。相机标定的目标找一个合适的数学模型来表达复杂的成像 过程,求出该模型的...
  • 最近在接手人脸姿态估计的相关工作,其中涉及了从人脸关键点到人脸姿态的估计,使用opencv solvePnP函数通过预定义3D(关键点)模板与二维关键点进行旋转矩阵求解过程中涉及到需使用相机内参,那么相机内参是什么,...
  • 神经网络学习小记录55——Pytorch 搭建自己的facenet人脸识别平台学习前言什么是facenet源码下载facenet的实现思路一、预测部分1、主干网络介绍2、根据特征获得128特征向量3、l2标准化、训练部分1、数据集介绍2...
  • 不得不说,我这个菜鸡做这题真的懵,自己也没想明白,看别人的看明白了,但是有的部分我还是不明白,先说一下这道题为什么要开二维数组,我个人认为这样的 为什么要开二维数组: S == 0(类型0)时,如果你...
  • 通过相机内参,可以将相机坐标系中的三维点投影到图像坐标系中的二维点。 设相机内参矩阵IM = [[fx, 0, cx], [0, fy, cy], [0, 0, 1]], 其中fx, fy为相机焦距。 cx, cy为主点的坐标,主点表示相机光轴与成像平面...
  • 我们假设把每一个图片看作二维空间中的一个点,并且现有图片会满足于某个数据分布, 我们记作????????????????????(????)。以人脸举例,在很大的一个图像分布空间中,实际上只有很小一部分的区域人脸图像。如上图...
  • 因为这确实听刁钻,许多人不会打二维线段树,却一直在想线段树怎么打,可悲(大佬:花了5分钟打出二维线段树,好难!),那摸,大家,这道题怎么做? 接下来会涉及到离散化与线段树,请自学,抱歉⊙﹏⊙ 那么...
  • FABEMD一种快速自适应的BEMD(Bidimensional Empirical Mode Decomposition,二维经验模式分解)方法,它能够将图像分解为不同尺度的高频图像和低频图像,高频图像代表了人脸皮肤细节纹理特征,而低频图像则代表了...
  • 上一个笔记主要讲了PCA的原理,并给出了二维图像降一维的示例代码。但还遗留了以下几个问题: 在计算协方差和特征向量的方法上,书上使用的一种被作者称为compact trick的技巧,以及奇异值分解(SVD),...
  • 2018.7.9比赛

    2018-07-09 14:55:26
    一开始题目看错了以为二维不可做直接跳过 最后15分钟发现裸的背包加个二进制优化 赶紧敲总算还是来得及 O(n∗p∗log(100))O(n∗p∗log(100))O( n * p * log(100)) 菜了菜了 还学到了单调队列优化这个dp...
  • 基于弹性束图匹配的人脸识别…

    千次阅读 2013-10-08 16:19:43
    原文地址:基于弹性束图匹配的人脸识别(自己做的ppt)作者:雪后DE阳光主要内容 1、EBGM的提出 2、弹性图匹配 3、弹性束图匹配 ...1997年,wiskott等使用二维结构的Gabor小波对人脸图像进行处理,将人脸表
  • 识别即分类,分类的hello world自然是二分类了。哈哈哈 真考虑人体骨架什么的真的太复杂,因为人定义的概念很宽泛的,比如说...几年前书发了3卷积视频分类的一个东西,不说benchmark或landmark,或state of...
  • 第一题二维背包。 第二题正解关于数论,很难想。暴力非常好敲,我还卡了一发常数。最后还没有别人骗分的高。回去开longlong,50分。 第三题全排列,我恰好知道STL可求,轻松过样例。后面测试自己手造数据时WA,一...

空空如也

空空如也

1 2 3
收藏数 60
精华内容 24
关键字:

二维脸是什么