2018-04-18 00:14:42 AnimateX 阅读数 3273

图像边缘Canny算子提取


Blog: https://blog.csdn.net/AnimateX

Email: animatex.deng@gmail.com

这里写图片描述

本次项目中我觉得最有意思的部分就是梯度计算和边缘提取,如何提取更精细更准确的边缘则是最终的目的!


1 边缘检测

1.1 边缘定义

边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括:
(1) 深度上的不连续;
(2) 表面方向不连续;
(3) 物质属性变化;
(4) 场景照明变化。

1.2 边缘检测方法分类

边缘检测是图像处理和计算机视觉中,尤其是特征检测中的一个研究领域。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。有许多方法用于边缘检测,它们的绝大部分可以划分为两类:
(1) 基于查找一类;
(2) 基于零穿越的一类。
基于查找的方法通过寻找图像一阶导数中的最大和最小值来检测边界,通常是将边界定位在梯度最大的方向。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界,通常是Laplacian过零点或者非线性差分表示的过零点。

1.3 影响边缘的因素

自然界图像的边缘并不总是理想的阶梯边缘。相反,它们通常受到一个或多个下面所列因素的影响:
(1) 有限场景深度带来的聚焦模糊.
(2) 非零半径光源产生的阴影带来的半影模糊.
(3) 光滑物体边缘的阴影.
(4) 物体边缘附近的局部镜面反射或者漫反射.

如果将边缘认为是一定数量点亮度发生变化的地方,那么边缘检测大体上就是计算这个亮度变化的导数。为简化起见,我们可以先在一维空间分析边缘检测。在这个例子中,我们的数据是一行不同点亮度的数据。例如,在下面的1维数据中我们可以直观地说在第4与第5个点之间有一个边界:

5764152148149

除非场景中的物体非常简单并且照明条件得到了很好的控制,否则确定一个用来判断两个相邻点之间有多大的亮度变化才算是有边界的阈值,并不是一件容易的事。实际上,这也是为什么边缘检测不是一个简单问题的原因之一。

2 边缘检测常见方法

有许多用于边缘检测的方法,他们大致可分为两类:基于搜索和基于零交叉。

基于搜索的边缘检测方法首先计算边缘强度,通常用一阶导数表示,例如梯度模;然后,用计算估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值。

基于零交叉的方法找到由图像得到的二阶导数的零交叉点来定位边缘.通常用拉普拉斯算子或非线性微分方程的零交叉点,我们将在后面的小节中描述。

滤波做为边缘检测的预处理通常是必要的,通常采用高斯滤波。
已发表的边缘检测方法应用计算边界强度的度量,这与平滑滤波有本质的不同。正如许多边缘检测方法依赖于图像梯度的计算,他们用不同种类的滤波器来估计x-方向和y-方向的梯度。

2.1 一阶微分梯度算子

许多边缘检测操作都是基于亮度的一阶导数,方便得到原始数据的亮度梯度。一阶微分边缘算子也称为梯度边缘算子,它是利用图像在边缘处的阶跃性,即图像梯度在边缘取得极大值的特性进行边缘检测。梯度是一个矢量,它具有方向 θ 和模 |ΔI|

ΔI=(IxIy)

|ΔI|=(Ix)2+(Iy)2=Ix2+Iy2

θ=arctan(Iy/Ix)

梯度的方向提供了边缘的趋势信息,因为梯度方向始终是垂直于边缘方向,梯度的模值大小提供了边缘的强度信息。

在实际使用中,通常利用有限差分进行梯度近似。对于上面的公式,我们有如下的近似:

Ix=limh0I(x+Δx,y)I(x,y)ΔxI(x+1,y)I(x,y),(Δx=1)

Iy=limh0I(x,y+Δxy)I(x,y)ΔyI(x,y+1)I(x,y),(Δy=1)

2.2 Robert梯度算子

1963年,Roberts提出了这种寻找边缘的算子。Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。在Roberts检测算子中:

Ix=I(i,j)I(i+1,j+1)

Iy=I(i+1,j)I(i,j+1)

则可以得到对应的x方向和y方向上的卷积核:
mx=[1001],my=[0110]

2.3 Prewitt梯度算子

利用周边八邻域的灰度值来估计中心梯度。其简化梯度计算公式:

Ix=I(i1,j+1)+I(i,j+1)+I(i+1,j+1)I(i1,j1)I(i,j1)I(i+1,j1)

Iy=I(i+1,j1)+I(i+1,j)+I(i+1,j+1)I(i1,j1)+I(i1,j)+I(i1,j+1)

对应的卷积核为:
mx=[10+110+110+1],my=[111000+1+1+1]

2.4 Sobel梯度算子

相比于Prewitt梯度算子,Sobel算子提高了靠近中间位置的权重。其对应的卷积核为:

mx=[10+120+210+1],my=[121000+1+2+1]

3 二阶微分梯度算子

由一阶微分可知,边缘即是图像的一阶导数局部最大值的地方,那么也意味着该点的二阶导数为零。二阶微分边缘检测算子就是利用图像在边缘处的阶跃性导致图像二阶微分在边缘处出现零值这一特性进行边缘检测的。
对于图像的二阶微分可以用拉普拉斯算子来表示:

2I=2Ix2+2Iy2

我们在像素点(i,j)的3×3的邻域内,可以有如下的近似:
2Ix2=I(i,j+1)2I(i,j)+I(i,j1)

2Iy2=I(i+1,j)2I(i,j)+I(i1,j)

2I=4I(i,j)+I(i,j+1)+I(i,j1)+I(i+1,j)+I(i1,j)

对应的二阶微分卷积核为:
m=[010141010]

所以二阶微分检测边缘的方法就分两步:1)用上面的Laplace核与图像进行卷积;2)对卷积后的图像,去掉那些卷积结果为0的点。

虽然上述使用二阶微分检测边缘的方法简单,但它的缺点是对噪声十分敏感,同时也没有能够提供边缘的方向信息。为了实现对噪声的抑制,Marr等提出了LOG的方法。

为了减少噪声对边缘的影响,首先图像要进行低通滤波,LOG采用了高斯函数作为低通滤波器。高斯函数为:

G(x,y)=12πσ2ex2+y22σ2

上面的公式中σ决定了对图像的平滑程度。高斯函数生成的滤波模板尺寸一般设定为6σ+1(加1是会了使滤波器的尺寸为奇数)。使用高斯函数对图像进行滤波并对图像滤波结果进行二阶微分运算的过程,可以转换为先对高斯函数进行二阶微分,再利用高斯函数的二阶微分结果对图像进行卷积运算:
2[G(x,y)f(x,y)]=2[G(x,y)]f(x,y)

2G(x,y)=12πσ4[1x2+y2σ2]exp(x2+y22σ2)

4 Canny 边缘检测

canny边缘检测实际上是一种一阶微分算子检测算法,但为什么这里拿出来说呢,因为它几乎是边缘检测算子中最为常用的一种,也是个人认为现在最优秀的边缘检测算子。Canny提出了边缘检测算子优劣评判的三条标准:

高的检测率。边缘检测算子应该只对边缘进行响应,检测算子不漏检任何边缘,也不应该将非边缘标记为边缘。
精确定位。检测到的边缘与实际边缘之间的距离要尽可能的小。
明确的响应。对每一条边缘只有一次响应,只得到一个点。
Canny边缘检测之所以优秀是因为它在一阶微分算子的基础上,增加了非最大值抑制和双阈值两项改进。利用非极大值抑制不仅可以有效地抑制多响应边缘,而且还可以提高边缘的定位精度;利用双阈值可以有效减少边缘的漏检率。

Canny边缘检测主要分四步进行:
1 去噪声;
2 计算梯度与方向角;
3 非最大值抑制;
4 滞后阈值化;
其中前两步很简单,先用一个高斯滤波器对图像进行滤波,然后用Sobel水平和竖直检测子与图像卷积,来计算梯度和方向角。

4.1 去噪

一般用高斯滤波。高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。其本质是一个低通滤波器,查看其模板可以看出。
注:理论上,高斯分布在所有定义域上都有非负值,这就需要一个无限大的卷积核。在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。通常,图像处理程序只需要计算 (6σ+1)×(6σ+1)(6σ+1)×(6σ+1)的矩阵就可以保证相关像素影响。对于边界上的点,通常采用复制周围的点到另一面再进行加权平均运算。
matlab实现举例:

w   = fspecial('gausian', [3 3], 0.5);
img = imfilter(I, w, 'replicate');

4.2 计算梯度与方向角

利用前面讲的Sobel算子计算。
MATLAB实现:

wx_sobel = [1 0 -1; 2 0 -2; 1 0 -1];
wy_sobel = [1 2 1; 0 0 0; -1 -2 -1];
edgeX = imfilter(tempEdge, wx_sobel, 'replicate');
edgeY = imfilter(tempEdge, wy_sobel, 'replicate');
imgEdge(:, :, i) = sqrt(edgeX .^ 2 + edgeY .^ 2);
% for speed
% imgEdge(:, :, i) = abs(edgeX) + abs(edgeY);

thetaTemp( edgeX ~= 0 ) = atan( edgeY ./ edgeX );

4.3 非极大值抑制

图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(也许两边的图像刚好在增加或者减小,表现出来增长的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点(边缘)局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。

非极大值抑制

先对上图作一个说明,每一个格子代表一个像素点,P(i,j)为坐标系原点,绿色的线为我的理想边缘,我们要检查A点是不是我们的边缘,那么需要看其是不是梯度方向上的最大值。蓝色线条为梯度方向(垂直于边缘方向),要确认A点是不是梯度方向的极大值,简单办法就是看A点是不是比梯度方向左右两边的点都大。

非极大值抑制原理说明:已知需要判断的点A的值和对应的梯度方向,找到A点在正负梯度方向两个邻近点B、C的值,比较A点的值是不是比B、C都大,如果不满足该条件就将A点的值置为最小值(一般是0,对于红外图像需要处理)。

问题在于B C两点不一定在0°、45°、90°、135°几个方向上,那怎么比较?

0~45,45~90

除了在梯度方向刚好在0°、45°、90°、135°几个方向时,B、C在特殊点。其他点无法直接得到,那么只能通过插值得到。如上图所示,我们要要得到B、C两点的值也就是M1和M2,我们已知M1、M2两点最邻近两点的值和x和y方向的梯度值,那么权重可以用Gx/Gy表示,用线性插值公式:
weight=|Gx,y||Gy,x|

M=P0×weight+(1weight)×P1

则我们可以得到如上图所示的计算公式,代码如下:

% 代码计算梯度角预先加上pi/2, 因为atan计算出来的范围是[-pi/2, pi/2]
% 0 ~ pi/4
M1 = p(i-1, j+1) * weight + (1 - weight) * p(i, j+1);
M2 = p(i+1, j-1) * weight + (1 - weight) * p(i, j-1);
% pi/4 ~ pi/2
M1 = p(i-1, j+1) * weight + (1 - weight) * p(i-1, j);
M2 = p(i+1, j-1) * weight + (1 - weight) * p(i+1, j);

同理,下图也能用同样的方法进行计算:

90~135,135~pi

90°到135°,135°到180°,M1和M2值计算的代码如下:

% 代码计算梯度角预先加上pi/2, 因为atan计算出来的范围是[-pi/2, pi/2]
% pi/2 ~ 3pi/2
M1 = p(i-1, j-1) * weight + (1 - weight) * p(i-1, j);
M2 = p(i+1, j+1) * weight + (1 - weight) * p(i+1, j);
% 3pi/2 ~ pi
M1 = p(i-1, j-1) * weight + (1 - weight) * p(i, j-1);
M2 = p(i+1, j+1) * weight + (1 - weight) * p(i, j+1);

4.4 滞后阈值化

由于噪声的影响,经常会在本应该连续的边缘出现断裂的问题。滞后阈值化设定两个阈值:一个为高阈值Th,一个为低阈值Tl。如果任何像素边缘算子的影响超过高阈值,将这些像素标记为边缘。所以不整个过程描述如下:

如果该像素的梯度值小于Tl,则该像素为非边缘像素;
如果该像素的梯度值大于Th,则该像素为边缘像素;

但是阈值怎么设置呢?我们先来看 MATLAB 的edge函数,其中有这么一段

function [lowThresh, highThresh] = selectThresholds(thresh, magGrad, PercentOfPixelsNotEdges, ThresholdRatio, ~)

[m,n] = size(magGrad);

% Select the thresholds
if isempty(thresh)
    % 计算统计直方图,灰阶设置为6阶
    counts=imhist(magGrad, 64);
    % 设置累积直方图的总数大于0.7总像素点数的归一化值为高阈值
    highThresh = find(cumsum(counts) > PercentOfPixelsNotEdges*m*n,...
        1,'first') / 64;
    % 低阈值为0.4*highThresh
    lowThresh = ThresholdRatio*highThresh;
elseif length(thresh)==1
    highThresh = thresh;
    if thresh>=1
        error(message('images:edge:thresholdMustBeLessThanOne'))
    end
    lowThresh = ThresholdRatio*thresh;
elseif length(thresh)==2
    lowThresh = thresh(1);
    highThresh = thresh(2);
    if (lowThresh >= highThresh) || (highThresh >= 1)
        error(message('images:edge:thresholdOutOfRange'))
    end
end

可见光图像我没有测试,我对14位的原始数据测试,直方图太奇特,无法用此种方法实现。变为固定阈值。

4.5 弱边缘检测(连通性分析)

再完成双阈值处理后,响应超过低阈值(高低阈值之间)的像素,如果与已经标记为边缘的像素4-邻接或8-邻接,则将这些像素也标记为边缘。
如果该像素的梯度值介于Tl与Th之间,需要进一步检测该像素的3×3邻域内的8个点,如果这8个点内有一个或以上的点梯度超过了Th,则该像素为边缘像素,否则不是边缘像素。
这种方法一样不适合红外灰度数据分析。

5 总结

Canny最重要的是非极大值抑制和阈值处理以及弱边缘检测。
可是如果在FPGA中做呢?如何做?

  1. 关于非极大值抑制
    这里写图片描述
    如上图所示,我们将区域变为①~⑤,直接用临近点代替M1和M2的值。
  2. 阈值处理
    考虑从均值入手(考虑红外的背景问题)。

CODE

function imgOut = edgeDetecNew(imgIn, rad, Sigma)
    [M, N, num] = size(imgIn);
    imgOut = zeros(M, N, num);
    %
    % Version 2.0
    % Date:2018-04-18
    % Author: Deng
    % This is test algorithm
    % Use Canny operator
    %--------------------------------------------------------------%
    %---------------------- algorithm steps -----------------------%
    % 1. Gaussian smoothing of the original image
    % 2. Perform sobel edge detection on Gaussian-smoothed images.
    %    There is also a need for horizontal and vertical joints.
    % 3. Non-maximally suppression of joint sobel detection images
    % 4. Connect edge points and perform hysteresis thresholding
    %--------------------------------------------------------------%

    w_gauss = fspecial('gaussian', [rad rad], Sigma);
    gaussTemp = zeros(M, N, num);

    % sobel operator
    wx_sobel = [1 0 -1; 2 0 -2; 1 0 -1];
    wy_sobel = [1 2 1; 0 0 0; -1 -2 -1];

    Theta    = zeros(M, N, num);
    imgEdge  = zeros(M, N, num);
    for i = 1 : num
        temp = imgIn(:, :, i);
        gaussTemp(:, :, i) = imfilter(temp, w_gauss, 'replicate');
        figure, imshow(gaussTemp, [ ]);
        % Theta is Theta is the angle between edgeY and edgeX.
        thetaTemp = Theta(:, :, i);
        tempEdge  = gaussTemp(:, :, i);
        edgeX = imfilter(tempEdge, wx_sobel, 'replicate');
        edgeY = imfilter(tempEdge, wy_sobel, 'replicate');
        imgEdge(:, :, i) = sqrt(edgeX .^ 2 + edgeY .^ 2);
        % for speed
        % imgEdge(:, :, i) = abs(edgeX) + abs(edgeY);

        thetaTemp( edgeX ~= 0 ) = atan( edgeY ./ edgeX );

        other = thetaTemp( edgeX == 0 );
        thetaTemp( edgeY( other ) > 0 ) = pi / 2;
        thetaTemp( edgeY( other ) < 0 ) = - pi /2;
        Theta(:, :, i) = thetaTemp;
    end


    % The key two steps
    % Non-maximally suppression
    for i = 1 : num
        thetaTemp = Theta(:, :, i);
        edgeTemp  = imgEdge(:, :, i);
        Edge  = padarray(edgeTemp,  [1 1], 'replicate');
        theta = padarray(thetaTemp, [1 1], 'replicate');

        %------------------test Ir flag------------------%
        % flag  = mean( Edge(:) );
        flag = 0;
        %------------------------------------------------%

        for j = 2 : M+1
            for k = 2 : N+1
                interP = Edge(j-1 : j+1, k-1 : k+1);
                % The first interpolation point: M1
                % The second interpolation point: M2
                if theta(j, k) == -pi/2
                    M1 = interP(3, 2);
                    M2 = interP(1, 2);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                elseif theta(j, k) > -pi/2 && theta(j, k) < -pi /4
                    weight = abs( edgeX(j-1, k-1) ) / abs( edgeY(j-1, k-1) );
                    M1 = weight * interP(3, 3) + (1 - weight) * interP(3, 2);
                    M2 = weight * interP(1, 1) + (1 - weight) * interP(1, 2);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                elseif theta(j, k) >= -pi /4 && theta(j, k) < 0
                    weight = abs( edgeY(j-1, k-1) ) / abs( edgeX(j-1, k-1) );
                    M1 = weight * interP(3, 3) + (1 - weight) * interP(2, 3);
                    M2 = weight * interP(1, 1) + (1 - weight) * interP(2, 1);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                elseif theta(j, k) == 0
                    M1 = interP(2, 3);
                    M2 = interP(2, 1);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                elseif theta(j, k) > 0 && theta(j, k) < pi / 4
                    weight = abs( edgeY(j-1, k-1) ) / abs( edgeX(j-1, k-1) );
                    M1 = weight * interP(1, 3) + (1 - weight) * interP(2, 3);
                    M2 = weight * interP(3, 1) + (1 - weight) * interP(2, 1);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                elseif theta(j, k) >= pi/4 && theta(j, k) < pi/2
                    weight = abs( edgeX(j-1, k-1) ) / abs( edgeY(j-1, k-1) );
                    M1 = weight * interP(1, 3) + (1 - weight) * interP(1, 2);
                    M2 = weight * interP(3, 1) + (1 - weight) * interP(3, 2);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                else
                    M1 = interP(1, 2);
                    M2 = interP(3, 2);
                    if interP(2, 2) < M1 || interP(2, 2) < M2
                        Edge(j, k) = flag;
                    end
                end
            end
        end
        figure, imshow(Edge, [ ]);

        % Double threshold processing
        % The first steps: find th by use hist.

        %------------------------- Test Code -------------------------%
        HistTemp = Edge(2 : M+1, 2 : N+1);
        meanVaule = mean( HistTemp(:) );
        maxVaule = max( HistTemp(:) );

        % XX需要根据实际情况调整
        th = XX * meanVaule;
        tl = XX * th;

        HistTemp( HistTemp <=  tl ) = flag;
        HistTemp( HistTemp >= th ) = maxVaule;
        HistTemp = imfilter(HistTemp, w_gauss, 'replicate');
        figure, imshow(HistTemp, [ ]);

        for j = 1 : M-2
            for k = 1 : N-2
                connectTemp = HistTemp(j : j+2, k : k+2);
                if HistTemp(j, k) > tl && HistTemp(j, k) < th
                    if numel( connectTemp >= maxVaule ) > 1
                        HistTemp(j, k) =  maxVaule;
                    else
                        HistTemp(j, k) = flag;
                    end
                end
            end
        end
        %--------------------------------------------------------------%
        imgOut(:, :, i) = HistTemp;
    end
end

Reference

[1] https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A
[2] https://zh.wikipedia.org/wiki/Canny%E7%AE%97%E5%AD%90
[3] https://zh.wikipedia.org/wiki/%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B
[4] https://blog.csdn.net/likezhaobin/article/details/6892176
[5] http://www.cnblogs.com/tiandsp/archive/2012/12/13/2817240.html

2017-12-26 22:11:54 Tong_T 阅读数 53033

摘要: 随着圣诞的到来,大家纷纷@官方微信给自己的头像加上一顶圣诞帽。当然这种事情用很多P图软件都可以做到。但是作为一个学习图像处理的技术人,还是觉得我们有必要写一个程序来做这件事情。而且这完全可以作为一个练手的小项目,工作量不大,而且很有意思。

用到的工具:
OpenCV
dlib(dlib的人脸检测比OpenCV更好用,而且dlib有OpenCV没有的关键点检测。)
用到的语言为Python。

流程

一、素材准备

首先我们需要准备一个圣诞帽的素材,格式最好为PNG,因为PNG的话我们可以直接用Alpha通道作为掩膜使用。我们用到的圣诞帽如下图:
这里写图片描述
我们通过通道分离可以得到圣诞帽图像的alpha通道。代码如下:
这里写图片描述
为了能够与rgb通道的头像图片进行运算,我们把rgb三通道合成一张rgb的彩色帽子图。Alpha通道的图像如下图所示。
这里写图片描述

整个代码流程如下:

  1. 人脸检测与人脸关键点检测
  2. 调整帽子大小
  3. 提取帽子和需要添加帽子的区域
  4. 添加圣诞帽

我们用下面这张图作为我们的测试图片。
这里写图片描述

import cv2
import dlib


# 给img中的人头像加上圣诞帽,人脸最好为正脸
def add_hat(img, hat_img):
    # 分离rgba通道,合成rgb三通道帽子图,a通道后面做mask用
    r, g, b, a = cv2.split(hat_img)
    rgb_hat = cv2.merge((r, g, b))
    cv2.imwrite("hat_alpha.jpg", a)

    # dlib人脸关键点检测器
    predictor_path = "shape_predictor_5_face_landmarks.dat"
    predictor = dlib.shape_predictor(predictor_path)

    # dlib正脸检测器
    detector = dlib.get_frontal_face_detector()

    # 正脸检测
    dets = detector(img, 1)

    # 如果检测到人脸
    if len(dets) > 0:
        for d in dets:
            x, y, w, h = d.left(), d.top(), d.right() - d.left(), d.bottom() - d.top()
            # x,y,w,h = faceRect
            # cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2,8,0)

            # 关键点检测,5个关键点
            shape = predictor(img, d)
            # for point in shape.parts():
            #     cv2.circle(img,(point.x,point.y),3,color=(0,255,0))

            # cv2.imshow("image",img)
            # cv2.waitKey()

            # 选取左右眼眼角的点
            point1 = shape.part(0)
            point2 = shape.part(2)

            # 求两点中心
            eyes_center = ((point1.x + point2.x) // 2, (point1.y + point2.y) // 2)

            # cv2.circle(img,eyes_center,3,color=(0,255,0))
            # cv2.imshow("image",img)
            # cv2.waitKey()

            #  根据人脸大小调整帽子大小
            factor = 1.5
            resized_hat_h = int(round(rgb_hat.shape[0] * w / rgb_hat.shape[1] * factor))
            resized_hat_w = int(round(rgb_hat.shape[1] * w / rgb_hat.shape[1] * factor))

            if resized_hat_h > y:
                resized_hat_h = y - 1

            # 根据人脸大小调整帽子大小
            resized_hat = cv2.resize(rgb_hat, (resized_hat_w, resized_hat_h))

            # 用alpha通道作为mask
            mask = cv2.resize(a, (resized_hat_w, resized_hat_h))
            mask_inv = cv2.bitwise_not(mask)

            # 帽子相对与人脸框上线的偏移量
            dh = 0
            dw = 0
            # 原图ROI
            # bg_roi = img[y+dh-resized_hat_h:y+dh, x+dw:x+dw+resized_hat_w]
            bg_roi = img[y + dh - resized_hat_h:y + dh,
                     (eyes_center[0] - resized_hat_w // 3):(eyes_center[0] + resized_hat_w // 3 * 2)]

            # 原图ROI中提取放帽子的区域
            bg_roi = bg_roi.astype(float)
            mask_inv = cv2.merge((mask_inv, mask_inv, mask_inv))
            alpha = mask_inv.astype(float) / 255

            # 相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)
            alpha = cv2.resize(alpha, (bg_roi.shape[1], bg_roi.shape[0]))
            # print("alpha size: ",alpha.shape)
            # print("bg_roi size: ",bg_roi.shape)
            bg = cv2.multiply(alpha, bg_roi)
            bg = bg.astype('uint8')

            cv2.imwrite("bg.jpg", bg)
            # cv2.imshow("image",img)
            # cv2.waitKey()

            # 提取帽子区域
            hat = cv2.bitwise_and(resized_hat, resized_hat, mask=mask)
            cv2.imwrite("hat.jpg", hat)

            # cv2.imshow("hat",hat)
            # cv2.imshow("bg",bg)

            # print("bg size: ",bg.shape)
            # print("hat size: ",hat.shape)

            # 相加之前保证两者大小一致(可能会由于四舍五入原因不一致)
            hat = cv2.resize(hat, (bg_roi.shape[1], bg_roi.shape[0]))
            # 两个ROI区域相加
            add_hat = cv2.add(bg, hat)
            # cv2.imshow("add_hat",add_hat)

            # 把添加好帽子的区域放回原图
            img[y + dh - resized_hat_h:y + dh,
            (eyes_center[0] - resized_hat_w // 3):(eyes_center[0] + resized_hat_w // 3 * 2)] = add_hat

            # 展示效果
            # cv2.imshow("img",img )
            # cv2.waitKey(0)

            return img


# 读取帽子图,第二个参数-1表示读取为rgba通道,否则为rgb通道
hat_img = cv2.imread("hat2.png", -1)

# 读取头像图
img = cv2.imread("IMG_9891.JPG")
output = add_hat(img, hat_img)

# 展示效果
cv2.imshow("output", output)
cv2.waitKey(0)
cv2.imwrite("output1.jpg", output)
# import glob as gb

# img_path = gb.glob("./images/*.jpg")

# for path in img_path:
#     img = cv2.imread(path)

#     # 添加帽子
#     output = add_hat(img,hat_img)

#     # 展示效果
#     cv2.imshow("output",output )
#     cv2.waitKey(0)

cv2.destroyAllWindows()

展示效果如下:
这里写图片描述

原始网址:https://mp.weixin.qq.com/s?__biz=MzIwNDA1OTM4NQ==&mid=2649541013&idx=1&sn=3fdfa05d6f1063e6dbbb97b0ef2a2d84&chksm=8edd8e03b9aa0715140a76c7ba658eaeb5176d4d67c1107b5d73e93d8b8f8fd6e8ee7e785e8b&mpshare=1&scene=23&srcid=1225NejVqeFsxNPPXYtHC5Yt#rd

完整代码的Github地址:

https://github.com/LiuXiaolong19920720/Add-Christmas-Hat

2019-07-23 14:54:34 qq_15799673 阅读数 49

读研期间对视频图像处理很感兴趣,从研一开始自学Python和AI相关的知识,到目前面临找工作时期,才发现做项目学习这么久的过程中一直在看别人的博客学习,然而没有好好写一次博客,于是把自己做的一个项目简单地介绍下相当于一个纪念吧。另外项目的传送门:https://github.com/junchou9463

1 概述

对人脸的研究一直是个很有意思的话题,从刚开始的人脸特征提取,如Hog特征,然后根据Hog特征进行人脸识别,行人检测等一系列的应用。这些都是很早的研究,后面我根据这些年对人脸的研究做了一系列的开发应用,包括:1.人脸比对识别登陆 2.人脸性别,表情识别 3.人脸眼动跟踪识别 4.人脸姿态识别 5.人脸运动单元的识别 6.人的心率呼吸速率血氧饱和度的获取 7.人脸的血流标注 8.程序打包等。接下来是相关的介绍。

2 人脸比对识别登陆

Dlib库有个方法是 face_recognition_model_v1.compute_face_descriptor,可以通过Cnn获取人脸128维的特征,所以我们可以将摄像头获取的人脸图像进行特征提取然后和数据库里的进行比对,找出偏差最小的那一项对应的姓名即是用户名,下面是相应的代码:

rects = detector(rgbImage, 0)
if len(rects)> 0:
  largest_face=max(rects, key=lambda rect: rect.width() * rect.height())
  shape = predictor(rgbImage, largest_face)
  face_rec = face_rec_model.compute_face_descriptor(rgbImage, shape)
  files = os.listdir(path)
  for file in files:  # 遍历文件夹
      video_folder = os.path.join(path, file)
      data1 = np.load(video_folder)
      diff = np.linalg.norm(data1 - data2)
      if (diff < 0.6):
         (name, format_name) = file.split('.')
         return name

 

3 人脸性别,表情识别

通过Keras训练IMDB和fer2013数据库可以得到性别和表情的分类模型,后面只需加载该训练模型,然后提取人脸区域进行识别分类即可,训练代码和调用方式如下:

# -*- coding:utf-8 -*-
# Author:Jun

from keras.callbacks import CSVLogger, ModelCheckpoint, EarlyStopping
from keras.callbacks import ReduceLROnPlateau
from keras.preprocessing.image import ImageDataGenerator

from models.cnn import mini_XCEPTION
from utils.datasets import DataManager
from utils.datasets import split_data
from utils.preprocessor import preprocess_input

# parameters
batch_size = 32
num_epochs = 10000
input_shape = (64, 64, 1)
validation_split = .2
verbose = 1
num_classes = 7
patience = 50
base_path = '../trained_models/emotion_models/'

# data generator
data_generator = ImageDataGenerator(
                        featurewise_center=False,
                        featurewise_std_normalization=False,
                        rotation_range=10,
                        width_shift_range=0.1,
                        height_shift_range=0.1,
                        zoom_range=.1,
                        horizontal_flip=True)

# model parameters/compilation
model = mini_XCEPTION(input_shape, num_classes)
model.compile(optimizer='adam', loss='categorical_crossentropy',
              metrics=['accuracy'])
model.summary()


datasets = ['fer2013']
for dataset_name in datasets:
    print('Training dataset:', dataset_name)

    # callbacks
    log_file_path = base_path + dataset_name + '_emotion_training.log'
    csv_logger = CSVLogger(log_file_path, append=False)
    early_stop = EarlyStopping('val_loss', patience=patience)
    reduce_lr = ReduceLROnPlateau('val_loss', factor=0.1,
                                  patience=int(patience/4), verbose=1)
    trained_models_path = base_path + dataset_name + '_mini_XCEPTION'
    model_names = trained_models_path + '.{epoch:02d}-{val_acc:.2f}.hdf5'
    model_checkpoint = ModelCheckpoint(model_names, 'val_loss', verbose=1,
                                                    save_best_only=True)
    callbacks = [model_checkpoint, csv_logger, early_stop, reduce_lr]

    # loading dataset
    data_loader = DataManager(dataset_name, image_size=input_shape[:2])
    faces, emotions = data_loader.get_data()
    faces = preprocess_input(faces)
    num_samples, num_classes = emotions.shape
    train_data, val_data = split_data(faces, emotions, validation_split)
    train_faces, train_emotions = train_data
    model.fit_generator(data_generator.flow(train_faces, train_emotions,
                                            batch_size),
                        steps_per_epoch=len(train_faces) / batch_size,
                        epochs=num_epochs, verbose=1, callbacks=callbacks,
                        validation_data=val_data)

调用方式:

facemotionPredictor_path=current_path + '/fer2013_mini_XCEPTION.119-0.65.hdf5'
emotion_classifier = load_model(facemotionPredictor_path, compile=False)
emotion_target_size = emotion_classifier.input_shape[1:3]
gray_face = preprocess_input(gray_face, False)
gray_face = np.expand_dims(gray_face, 0)
gray_face = np.expand_dims(gray_face, -1)
with graph.as_default():
    list_emotion = emotion_classifier.predict(gray_face)
    emotion_label_arg = np.argmax(list_emotion)
    # print(self.emotion_classifier.predict(gray_face))
    emotion_text = emotion_labels[emotion_label_arg]
    emotion_window.append(emotion_text)
    if len(emotion_window) > 10:
        emotion_window.pop(0)
    emotion_mode = mode(emotion_window)

这里有个坑,当将TensorFlow放在进程中调用模型时,会出现找不到tensor的错误,解决办法是使用with graph.as_default()语句或者在识别图像之前,先在开始随便加载一张图像做测试,这样TensorFlow才可以明白现在是在使用这个模型。另外就是由于训练模型是基于国外人的表情训练的,所以很多国内人的表情没有那么丰富,为了更准确地识别,可以基于68个人脸特征点的位移变化进行权重相加,比如嘴角的特征i的变化过程可以反应微笑或悲伤等等。

4 人脸眼动跟踪识别

参照 Rendering of Eyes for Eye-Shape Registration and Gaze Estimation该文章,文中对眼球模型进行了大量的训练,可以得到56个特征点,根据特征点的移动可以转换为眼动轨迹,文章如下:

对于眼睛附近面部模型的获取,使用了

             文章:Constrained Local Neural Fields for robust facial landmark detection in the wild 里面的CLNF特征,同样地直接调用训练后的模型进行眼动跟踪识别。

 

5.人脸姿态识别

对于姿态识别,则利用二维与三维的转换,这个里面需要获取摄像头的参数,但通常都是进行近似表示。先介绍下基本知识:

一、四元数的定义

通过旋转轴和绕该轴旋转的角度可以构造一个四元数:

二、欧拉角到四元数的转换 

三、四元数到欧拉角的转换

在3D 空间中,表示物体的旋转可以由三个欧拉角来表示:pitch围绕X轴旋转,叫俯仰角。 yaw围绕Y轴旋转,叫偏航角。 roll围绕Z轴旋转,叫翻滚角。

四、人脸姿态估计 

获得的人脸姿态信息用三个欧拉角(pitch,yaw,roll)表示。 首先定义一个6关键点的3D脸部模型(左眼角,右眼角,鼻尖,左嘴角,右嘴角,下颌),然后检测出图片中对应的6个脸部关键点,采用OpenCV的solvePnP函数解出旋转向量,最后将旋转向量转换为欧拉角。

这里有三个坐标系。各种面部特征的3D坐标是建立在世界坐标系中。如果我们知道旋转和平移(即姿势),我们可以将世界坐标中的3D点变换为相机坐标中的3D点。可以使用相机的固有参数(焦距,光学中心等)将相机坐标中的3D点投影到图像平面(即图像坐标系)上。

我们知道3D模型上的许多点(即(U,V,W)),但是我们不知道(X,Y,Z)。 我们只知道2D点的位置(即(x,y))。 在没有径向变形的情况下,图像坐标中点p的坐标(x,y)由下式给出:

S是一个未知的比例因子。它存在于等式中,因为在任何图像中我们不知道图像的深度。如果将3D中的任何点P连接到相机的中心o,则光线与图像平面相交的点p是P的图像。换句话说,使用上述等式只能获得(X,Y,Z)达到刻度s。

可以使用一种称为直接线性变换(DirectLinear Transform,DLT)的方法,但是DLT解决方案不会使正确的目标函数最小化。理想情况下,我们希望最大限度地减少以下描述的重新投射错误(reprojection error)。原来,还有原则的方法迭代地改变R和t的值,以使重新投射错误减少。一种这样的方法称为Levenberg-Marquardt优化。

在OpenCV中,函数solvePnP和solvePnPRansac可用于估计姿态。 solvePnP实现了几种用于姿态估计的算法,可以使用参数标志来选择。它本质上是DLT解决方案通过Levenberg-Marquardt优化。SOLVEPNP_P3P仅使用3点来计算姿势,只有在使用solvePnPRansac时才使用它。 solvePnPRansac与solvePnP非常相似,只是它使用随机样本一致性(RANSAC)来鲁棒估计姿势。当你怀疑几个数据点非常嘈杂时,使用RANSAC非常有用。

 

同样地,附上代码:

model_points = np.array ([
        (0.0, 0.0, 0.0),  # Nose tip
        (0.0, -330.0, -65.0),  # Chin
        (-225.0, 170.0, -135.0),  # Left eye left corner
        (225.0, 170.0, -135.0),  # Right eye right corne
        (-150.0, -150.0, -125.0),  # Left Mouth corner
        (150.0, -150.0, -125.0)  # Right mouth corner
    ])/4.5
def get_image_points(self,img):
    # gray = cv2.cvtColor( img, cv2.COLOR_BGR2GRAY )  # 图片调整为灰色
    # dets = detector(img, 0)
    img_size = img.shape
    focal_length = img_size[1]
    center = (img_size[1] / 2, img_size[0] / 2)
    camera_matrix = np.array(
        [[focal_length, 0, center[0]],
         [0, focal_length, center[1]],
         [0, 0, 1]], dtype="double"
    )
    dist_coeffs = np.zeros((4, 1))
    image_points = np.array([
        (self.landmark[30]),  # Nose tip
        (self.landmark[8]),  # Chin
        (self.landmark[36]),  # Left eye left corner
        (self.landmark[45]),  # Right eye right corne
        (self.landmark[48]),  # Left Mouth corner
        (self.landmark[54])  # Right mouth corner
    ], dtype="double")
    point_3d = []
    rear_size = 75
    rear_depth = 0
    point_3d.append((-rear_size, -rear_size, rear_depth))
    point_3d.append((-rear_size, rear_size, rear_depth))
    point_3d.append((rear_size, rear_size, rear_depth))
    point_3d.append((rear_size, -rear_size, rear_depth))
    point_3d.append((-rear_size, -rear_size, rear_depth))
    front_size = 100
    front_depth = 100
    point_3d.append((-front_size, -front_size, front_depth))
    point_3d.append((-front_size, front_size, front_depth))
    point_3d.append((front_size, front_size, front_depth))
    point_3d.append((front_size, -front_size, front_depth))
    point_3d.append((-front_size, -front_size, front_depth))
    point_3d = np.array(point_3d, dtype=np.float).reshape(-1, 3)
    (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix,
                                                                  dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE)
    Y, X, Z,pitch,yaw,roll  = self.get_euler_angle(rotation_vector)
    # euler_angle_str = 'Z:{}, Y:{}, X:{}'.format(Y, X, Z)
    (point2D, jacobian) = cv2.projectPoints(point_3d, rotation_vector,translation_vector, camera_matrix, dist_coeffs)
    point_2d = np.int32(point2D.reshape(-1, 2))

    cv2.polylines(img, [point_2d], True, color=(0, 0,255), thickness=2,lineType=cv2.LINE_AA)
    cv2.line(img, tuple(point_2d[1]), tuple(
        point_2d[6]), color=(0, 0, 255),thickness=2,lineType=cv2.LINE_AA)
    cv2.line(img, tuple(point_2d[2]), tuple(
        point_2d[7]), color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)
    cv2.line(img, tuple(point_2d[3]), tuple(
        point_2d[8]), color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)
    # cv2.putText(img, euler_angle_str, (0, 120 + 10), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 1)
    return round(pitch,2) , round(yaw*10,2), round(roll,2),Y,X,Z

def get_euler_angle(self,rotation_vector):
    # calculate rotation angles
    theta = cv2.norm(rotation_vector, cv2.NORM_L2)

    # transformed to quaterniond
    w = math.cos(theta / 2)
    x = math.sin(theta / 2) * rotation_vector[0][0] / theta
    y = math.sin(theta / 2) * rotation_vector[1][0] / theta
    z = math.sin(theta / 2) * rotation_vector[2][0] / theta

    ysqr = y * y
    # pitch (x-axis rotation)
    t0 = 2.0 * (w * x + y * z)
    t1 = 1.0 - 2.0 * (x * x + ysqr)
    pitch = math.atan2(t0, t1)

    # yaw (y-axis rotation)
    t2 = 2.0 * (w * y - z * x)
    # if t2 > 1.0:
    #     t2 = 1.0
    # if t2 < -1.0:
    #     t2 = -1.0
    yaw = math.asin(t2)

    # roll (z-axis rotation)
    t3 = 2.0 * (w * z + x * y)
    t4 = 1.0 - 2.0 * (ysqr + z * z)
    roll = math.atan2(t3, t4)

    # 单位转换:将弧度转换为度
    Y = int((pitch / math.pi) * 180)
    X = int((yaw / math.pi) * 180)
    Z = int((roll / math.pi) * 180)

    return Y, X, Z, pitch, yaw, roll

示例图:

接下来我们还会介绍人脸特征点识别还有人脸检测,人脸运动单元,心率呼吸获取等一系列知识,这在下一次更新中说明,另外附上几张软件的运行图:

 

2016-09-24 16:45:05 fei20121106 阅读数 1807

图像类

图像处理工具

支持常见的一些图片滤镜效果函数

支持常见的一些图片滤镜效果函数

一行代码实现 Android 图片 Lowpoly 效果

一行代码实现 Android 图片 Lowpoly 效果

Android的着色器和过滤器

Android的着色器和过滤器

Android 高性能实时模糊(Blur) View 效果

Android 高性能实时模糊(Blur) View 效果

又一个LowPoly图片, 另外这个还可以让图片变成沙画

又一个LowPoly图片, 另外这个还可以让图片变成沙画

控件类

View

锯齿边框,类似优惠劵效果的自定义 View

锯齿边框,类似优惠劵效果的自定义 View

仿支付宝芝麻信用分仪表盘效果

仿支付宝芝麻信用分仪表盘效果

Android环形进度条类似apple watch健身记录。

Android环形进度条类似apple watch健身记录。

让任何布局支持选中状态,类似 Checkbox 的属性

让任何布局支持选中状态,类似 Checkbox 的属性

模仿掌上英雄联盟能力分析效果

模仿掌上英雄联盟能力分析效果

Preference 中经常会遇到选颜色的问题,这个自定义组件,就是帮你解决Color Preferece 的问题。

Preference 中经常会遇到选颜色的问题,这个自定义组件,就是帮你解决Color Preferece 的问题。

拆轮子-唯美细腻的夕阳海浪动画

拆轮子-唯美细腻的夕阳海浪动画

马蜂窝

把RecyclerView撸成 马 蜂 窝

支持自动高亮关键字的 TextView,很实用。

支持自动高亮关键字的 TextView,很实用。

搜索

Android 搜索过度效果

Android 搜索过度效果

List

挺有意思的一个下拉刷新效果,可以看看源码学习一下实现类似效果的思路。

挺有意思的一个下拉刷新效果,可以看看源码学习一下实现类似效果的思路。

支持多种显示效果与无限滚动的RecyclerView

支持多种显示效果与无限滚动的RecyclerView

视差效果的 Item 滑动变大

视差效果的 Item 滑动变大

RecyclerView 卡片画廊效果

RecyclerView 卡片画廊效果

基于itemtouchhelper,实现左滑菜单,删除确认

基于itemtouchhelper,实现左滑菜单,删除确认

又一个显示recycleview复杂效果的库

又一个显示recycleview复杂效果的库

RecyclerView Snap 边缘停止效果,让你的 App 更人性化,更精致。

RecyclerView Snap 边缘停止效果,让你的 App 更人性化,更精致。

波动效果的索引侧边栏,支持左右手模式和自定义索引 (郭佳哲)

带波动效果的索引侧边栏,支持左右手模式和自定义索引

Toast && Dialog

给人眼前一亮的 Toast 效果

给人眼前一亮的 Toast 效果

Android 自定义漂亮 Dialog。

Android 自定义漂亮 Dialog。

评价 & 投票 & 开关

五星好评

五星好评

Material Design 风格的投票效果!

Material Design 风格的投票效果!

Android 版本的 Day & Night Switcher!之前 Gank 发布过 iOS 版本的。

Android 版本的 Day & Night Switcher!之前 Gank 发布过 iOS 版本的。

菜单

一个别致的环形菜单

一个别致的环形菜单

轮盘样式的 Fragment 菜单,可转动轮盘切换 Fragment

轮盘样式的 Fragment 菜单,可转动轮盘切换 Fragment

一个实现树形内容展示的库

一个实现树形内容展示的库

Android两级磁贴列表,拥有完美的不同级磁贴互相接触推动的效果,适合多级分类嵌套展示。

Android两级磁贴列表,拥有完美的不同级磁贴互相接触推动的效果,适合多级分类嵌套展示。

一个 Android Page 过渡效果 Demo 效果

一个 Android Page 过渡效果 Demo 效果

一个好看的Float菜单

一个好看的Float菜单。

Material Design 风格 底部导航栏

Material Design 风格 底部导航栏

简单而精致的 Fragment 菜单控件,它可以让你切换 Fragment 的时候眼前一亮

简单而精致的 Fragment 菜单控件,它可以让你切换 Fragment 的时候眼前一亮

自定义圆形菜单

自定义圆形菜单

二维码

生成二维码和从图片解析二维码库

生成二维码和从图片解析二维码库

将生成的二维码与选择的图片合成,生成的新二维码

将生成的二维码与选择的图片合成,生成的新二维码

编辑器

目前来看 Android 上最棒的一款富文本编辑器

目前来看 Android 上最棒的一款富文本编辑器。

时钟

纯用SVG做的Google I/O 2016那个炫酷的时钟

纯用SVG做的Google I/O 2016那个炫酷的时钟

仿锤子闹钟

仿锤子闹钟

进度

一个绚丽的loading动效分析与实现!

一个绚丽的loading动效分析与实现!

Android 仿应用宝的下载效果,你知道中间那个光条是怎么做出来的吗?

Android 仿应用宝的下载效果,你知道中间那个光条是怎么做出来的吗?

一个展示下载进度的View,下载完成和失败的时候会有酷酷的动画.

一个展示下载进度的View,下载完成和失败的时候会有酷酷的动画.

动画

双面翻转 View 效果

双面翻转 View 效果

仿微信风格的 滑动退回

仿微信风格的 滑动退回

兼容4.0以上版本的点击扩散动画库,几行代码实现Activity转场动画效果 及新增自定义界面转场动画~

兼容4.0以上版本的点击扩散动画库,几行代码实现Activity转场动画效果 及新增自定义界面转场动画~

摇晃的门牌效果,支持 Sensor 晃动。

摇晃的门牌效果,支持 Sensor 晃动。

简单实现夜间模式-渐变效果

简单实现夜间模式-渐变效果

超级漂亮的一个高亮功能提醒效果!

超级漂亮的一个高亮功能提醒效果!

渐变的下载按钮,发现不少小伙伴要做类似效果的

渐变的下载按钮,发现不少小伙伴要做类似效果的

掉落通知效果

掉落通知效果

一个酷炫的分享控件

一个酷炫的分享控件

雪花,粒子爆炸效果

雪花,粒子爆炸效果

Android 下雪效果

Android 下雪效果

可跟随手指滑动,进行响应式滑动的背景效果

可跟随手指滑动,进行响应式滑动的背景效果

使用Transition FrameWork实现有意义的转场动画

使用Transition FrameWork实现有意义的转场动画

Depth-LIB-Android

小说Android开源篇 第一章——终于等到你Depth-LIB-Android

通过 AnimateVectoreDrawer 实现的一些动画效果,看看代码,有没有新灵感。

通过 AnimateVectoreDrawer 实现的一些动画效果,看看代码,有没有新灵感。

一个超级炫酷的高亮功能提醒效果

一个超级炫酷的高亮功能提醒效果

超漂亮的 Animation Scale 动画设置效果

超漂亮的 Animation Scale 动画设置效果。

拆轮子-唯美细腻的夕阳海浪动画

拆轮子-唯美细腻的夕阳海浪动画

模块

相册

抽取自Telegram,并加入QQ相册选择风格的图片选择器,高效,低耗,响应快速。

抽取自Telegram,并加入QQ相册选择风格的图片选择器,高效,低耗,响应快速。。

简洁的图片裁剪小裤子

简洁的图片裁剪小裤子

对 ImageView 实现了全屏浏览效果,同时支持了图片缩放效果,和滑动消失效果。

对 ImageView 实现了全屏浏览效果,同时支持了图片缩放效果,和滑动消失效果。

基于 Bottom Sheet 实现的图片选择器,交互效果不错。

基于 Bottom Sheet 实现的图片选择器,交互效果不错。

Android拼图支持库,轻松实现Instagram Layout效果

Android拼图支持库,轻松实现Instagram Layout效果

介绍页

Material 风格的介绍页,在做介绍的同时,可以向用户申请权限,做的超漂亮!!

Material 风格的介绍页,在做介绍的同时,可以向用户申请权限,做的超漂亮!!

带有色彩渐变的新功能引导页

带有色彩渐变的新功能引导页

登录

Material-Design风格的动画注册登陆界面~够清爽~

Material-Design风格的动画注册登陆界面~够清爽~

一个很棒的两步登录界面设计

一个很棒的两步登录界面设计

异步并发类

RxJava + 权限申请

RxJava + 权限申请

FcPermissions:也许是目前最好的动态权限请求库

FcPermissions:也许是目前最好的动态权限请求库

事件

一个高效,使用方便,基于动态代理实现的Android事件总线库

一个高效,使用方便,基于动态代理实现的Android事件总线库

饿了么开发了一套面向业务逻辑的编程库-ToyRoom

饿了么开发了一套面向业务逻辑的编程库-ToyRoom

AS最新可用eventbus3插件

AS最新可用eventbus3插件

崩溃

Android 收集用户基本信息利器,比如 App 崩溃后的地理位置,GPS网络状态,当前页面截屏等等

Android 收集用户基本信息利器,比如 App 崩溃后的地理位置,GPS网络状态,当前页面截屏等等

App 崩溃后,帮助你恢复现场并查看崩溃信息的库,在开发阶段相当实用,建议大家集成进去节省些开发时间。

App 崩溃后,帮助你恢复现场并查看崩溃信息的库,在开发阶段相当实用,建议大家集成进去节省些开发时间。

热修复

美团 Android 热更新方案 Robust 详解

美团 Android 热更新方案 Robust 详解,Robust 是基于 Instant Run 实现的代码热更新。

基于 InstantRun 实现的一个 Android 热修复方案

基于 InstantRun 实现的一个 Android 热修复方案

受微信热修复原理激发的Android热修复与增量升级框架,简单易用。

受微信热修复原理激发的Android热修复与增量升级框架,简单易用。

Android热修复实践应用–AndFix

Android热修复实践应用–AndFix

应用瘦身

[Android 之美] 那些你不知道的APK 瘦身,让你的APK更小

那些你不知道的APK 瘦身,让你的APK更小

[Android 之美] App瘦身最佳实践

App瘦身最佳实践

其他

通过更简洁的方式启动 Activity、Service、Broadcast 等

通过更简洁的方式启动 Activity、Service、Broadcast 等

非常实用的省略控件,无emoji截断之乱码, 无characterstyle截断之不雅

非常实用的省略控件,无emoji截断之乱码, 无characterstyle截断之不雅

利用 AnimatedVectorDrawable 实现会动的 emoji。

利用 AnimatedVectorDrawable 实现会动的 emoji。

自动给 TextView 增加超链接的库

自动给 TextView 增加超链接的库,还可以处理 Hashtags (#) ,at 人(@),电话,邮件等等数据。 超有用!

Android 的录音库,可录制 pcm 和 wav,暂停/继续录制

Android 的录音库,可录制 pcm 和 wav,暂停/继续录制

利用 KeyStore 存储密码,加密 SharedPreference 的数据,保证安全性

利用 KeyStore 存储密码,加密 SharedPreference 的数据,保证安全性。

AES-256加密的SharedPreferences

AES-256加密的SharedPreferences

为你的应用程序加上数字角标

为你的应用程序加上数字角标

Toolbar 组件展示 Banner 图片效果。

Toolbar 组件展示 Banner 图片效果。

超轻量的Android端Router框架。可灵活配置规则。适用性广

超轻量的Android端Router框架。可灵活配置规则。适用性广

Android 角标组件效果

Android 角标组件效果

显示阳历,农历,节假日和二十四节气 实现对某月日期的单选或者多选的andorid 日历控件库

显示阳历,农历,节假日和二十四节气 实现对某月日期的单选或者多选的andorid 日历控件库

一款漂亮的 Bottom Sheet 选择器

一款漂亮的 Bottom Sheet 选择器

Android 指纹识别身份验证 Demo 一例

Android 指纹识别身份验证 Demo 一例

带有动态效果的表单引导进度条。

带有动态效果的表单引导进度条。

Demo

机票座位在线选择android

机票座位在线选择android

任阅小说阅读器

任阅小说阅读器,高仿追书神器,实现追书推荐、标签检索、3D仿真翻页效果、文章阅读、缓存章节、日夜间模式、文本朗读等功能。

一个完整的生日管理 App: UPMiss 开源了

一个完整的生日管理 App: UPMiss 开源了

第三方微博

第三方微博

一个超棒的 Android Music Player

一个超棒的 Android Music Player

 一个比较完整的【面对面快传】App 开源项目

一个比较完整的【面对面快传】App 开源项目

 一款超漂亮的基于 MD 设计的电影 App

一款超漂亮的基于 MD 设计的电影 App

含多语言,md解析,day night主题的多语言代码阅读器CoReader

含多语言,md解析,day night主题的多语言代码阅读器CoReader

android-UniversalMusicPlayer

github:https://github.com/googlesamples/android-UniversalMusicPlayer
这个开源项目展示了如何实现一个横跨各种Android平台的音乐播放器,包括手机,平板,汽车,手表,电视等。Google官方推出,跨平台开发必看项目。

LookLook

github:https://github.com/xinghongfei/LookLook
可以阅读知乎日报,网易头条,每日推送一张妹子图片和视频,是一个精美的阅读软件。遵循Google Meterial 设计风格,加入了一些5.0以上的新特性,阅读体验绝不逊色于官方的app。
架构: MVP
图片加载:Glide
网络请求:RxJava & Retrofit+okhttp
界面: 遵循Google Meterial 设计风格
其他: Cardview,RecycleView,Butterknife,PhotoView

Meizhi

github:https://github.com/drakeet/Meizhi
每天推送一张妹子图、一个小视频和一系列程序员精选文章,数据来源于代码家的干货集中营。唯一不足就是视频解析还有bug,声音视频无法同步,希望能尽快修复。
架构: MVC
图片加载:Picasso
网络请求:RxJava & Retrofit+okhttp
界面: 遵循Google Meterial 设计风格
其他:nineoldandroids,photo view,numberprogressbar,umeng-analytics,rxandroid,butterknife,otto

Material Animations

GitHub: https://github.com/lgvalle/Material-Animations
演示View的平移、缩放动画,activity进入和退出动画,界面间元素共享,并且开发者在README中,对动画原理进行了精讲,是学习动画很好的项目,项目代码量比较少,也很适合新手学习。

跨平台(Android,iOS,web)的 IM 开源项目

跨平台(Android,iOS,web)的 IM 开源项目

2018-10-02 19:18:47 qq_34042417 阅读数 1801

作为一个初级码农,什么百度云,阿里云,腾讯云都搞一搞,前几天看到百度的一个AI平台,挺有意思的,于是乎做了一个人脸识别的小例子。看起来挺牛逼的,做完之后你只会佩服百度的强大!

 

先展示下项目吧!

 

通过调用摄像头,实现获取人脸图像,然后一秒钟截取一张视频图像传至后台处理,处理完后则返回用户信息。通过这个样例可以让项目中的用户验证变得高大上。

 

那我们现在谈谈他是怎么实现的?

1.你得注册个百度云,创建一个应用

2.点击刚刚创建的应用,查看一下百度给你的接口。

查看这两个接口的URL是否包含该v3的字样,这就是他帮助文档的意思。

 

3.我们根据百度给的帮文档看看他具体是怎么实现的。

4.你用的是百度资源,首先得让他知道你是谁,然后他才给你使用。这也是token认证的作用。

 

package com.lb.service;

import org.json.JSONObject;

import com.lb.token.Token;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

/**
 * 获取token类
 */
public class GetToken {
	
	public static void SaveToken(String url) {
    	String token = getAuth();
    	System.out.println("token:"+token);
    	Token.token = token;
	}
	
	/**
     * 获取权限token
     * @return 返回示例:
     * {
     * "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567",
     * "expires_in": 2592000
     * }
     */
    public static String getAuth() {
        // 官网获取的 API Key 更新为你注册的
        String clientId = Token.token;
        // 官网获取的 Secret Key 更新为你注册的
        String clientSecret = Token.clientSecret;
        return getAuth(clientId, clientSecret);
    }

    /**
     * 获取API访问token
     * 该token有一定的有效期,需要自行管理,当失效时需重新获取.
     * @param ak - 百度云官网获取的 API Key
     * @param sk - 百度云官网获取的 Securet Key
     * @return assess_token 示例:
     * "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567"
     */
    public static String getAuth(String ak, String sk) {
        // 获取token地址
        String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
        String getAccessTokenUrl = authHost
                // 1. grant_type为固定参数
                + "grant_type=client_credentials"
                // 2. 官网获取的 API Key
                + "&client_id=" + ak
                // 3. 官网获取的 Secret Key
                + "&client_secret=" + sk;
        try {
            URL realUrl = new URL(getAccessTokenUrl);
            // 打开和URL之间的连接
            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.err.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String result = "";
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
            /**
             * 返回结果示例
             */
            System.err.println("result:" + result);
            JSONObject jsonObject = new JSONObject(result);
            String access_token = jsonObject.getString("access_token");
            return access_token;
        } catch (Exception e) {
            System.err.printf("获取token失败!");
            e.printStackTrace(System.err);
        }
        return null;
    }

}

 

5.获取到token后我们即可调用百度的接口。

根据帮助文档我们只需请求百度的接口,按照上述格式传递一些参数,他则返回对比数据给你

package com.lb.service;

import com.baidu.ai.aip.utils.HttpUtil;
import com.google.gson.Gson;
import com.lb.opj.Msg;
import com.lb.token.Token;
import com.baidu.ai.aip.utils.Base64Util;
import com.baidu.ai.aip.utils.FileUtil;
import com.baidu.ai.aip.utils.GsonUtils;

import java.util.*;

/**
* 人脸对比
*/
public class FaceMatch {

    /**
    * 重要提示代码中所需工具类
    * FileUtil,Base64Util,HttpUtil,GsonUtils请从
    * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
    * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
    * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
    * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
    * 下载
    */
    public static String match(String path,byte[] b) {
        // 请求url
        String url = "https://aip.baidubce.com/rest/2.0/face/v3/match";
        try {

            //byte[] bytes1 = FileUtil.readFileByBytes(path+"photo/up.png");
        	byte[] bytes1 = b;
            byte[] bytes2 = FileUtil.readFileByBytes(path+"photo/lanbing.jpg");
            String image1 = Base64Util.encode(bytes1);
            String image2 = Base64Util.encode(bytes2);

            List<Map<String, Object>> images = new ArrayList<>();

            Map<String, Object> map1 = new HashMap<>();
            map1.put("image", image1);
            map1.put("image_type", "BASE64");
            map1.put("face_type", "LIVE");
            map1.put("quality_control", "LOW");
            map1.put("liveness_control", "NORMAL");

            Map<String, Object> map2 = new HashMap<>();
            map2.put("image", image2);
            map2.put("image_type", "BASE64");
            map2.put("face_type", "LIVE");
            map2.put("quality_control", "LOW");
            map2.put("liveness_control", "NORMAL");

            images.add(map1);
            images.add(map2);

            String param = GsonUtils.toJson(images);

            // 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
            String accessToken = Token.token;

            String result = HttpUtil.post(url, accessToken, "application/json", param);
            System.out.println(result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static void Compare(String path,byte[] b) {
    	Gson g = new Gson();
    	Msg msg = g.fromJson(FaceMatch.match(path,b), Msg.class);
    	System.out.println("匹配得分:"+msg.showScore());
    	
    }
}

 

其中他用到了几个百度的类,具体如下:

package com.baidu.ai.aip.utils;

/**
 * Base64 宸ュ叿绫�
 */
public class Base64Util {
    private static final char last2byte = (char) Integer.parseInt("00000011", 2);
    private static final char last4byte = (char) Integer.parseInt("00001111", 2);
    private static final char last6byte = (char) Integer.parseInt("00111111", 2);
    private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
    private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
    private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
    private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

    public Base64Util() {
    }

    public static String encode(byte[] from) {
        StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
        int num = 0;
        char currentByte = 0;

        int i;
        for (i = 0; i < from.length; ++i) {
            for (num %= 8; num < 8; num += 6) {
                switch (num) {
                    case 0:
                        currentByte = (char) (from[i] & lead6byte);
                        currentByte = (char) (currentByte >>> 2);
                    case 1:
                    case 3:
                    case 5:
                    default:
                        break;
                    case 2:
                        currentByte = (char) (from[i] & last6byte);
                        break;
                    case 4:
                        currentByte = (char) (from[i] & last4byte);
                        currentByte = (char) (currentByte << 2);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
                        }
                        break;
                    case 6:
                        currentByte = (char) (from[i] & last2byte);
                        currentByte = (char) (currentByte << 4);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
                        }
                }

                to.append(encodeTable[currentByte]);
            }
        }

        if (to.length() % 4 != 0) {
            for (i = 4 - to.length() % 4; i > 0; --i) {
                to.append("=");
            }
        }

        return to.toString();
    }
}


package com.baidu.ai.aip.utils;

import java.io.*;

/**
 * 鏂囦欢璇诲彇宸ュ叿绫�
 */
public class FileUtil {

    /**
     * 璇诲彇鏂囦欢鍐呭锛屼綔涓哄瓧绗︿覆杩斿洖
     */
    public static String readFileAsString(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException(filePath);
        } 

        if (file.length() > 1024 * 1024 * 1024) {
            throw new IOException("File is too large");
        } 

        StringBuilder sb = new StringBuilder((int) (file.length()));
        // 鍒涘缓瀛楄妭杈撳叆娴�  
        FileInputStream fis = new FileInputStream(filePath);  
        // 鍒涘缓涓�涓暱搴︿负10240鐨凚uffer
        byte[] bbuf = new byte[10240];  
        // 鐢ㄤ簬淇濆瓨瀹為檯璇诲彇鐨勫瓧鑺傛暟  
        int hasRead = 0;  
        while ( (hasRead = fis.read(bbuf)) > 0 ) {  
            sb.append(new String(bbuf, 0, hasRead));  
        }  
        fis.close();  
        return sb.toString();
    }

    /**
     * 鏍规嵁鏂囦欢璺緞璇诲彇byte[] 鏁扮粍
     */
    public static byte[] readFileByBytes(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException(filePath);
        } else {
            ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
            BufferedInputStream in = null;

            try {
                in = new BufferedInputStream(new FileInputStream(file));
                short bufSize = 1024;
                byte[] buffer = new byte[bufSize];
                int len1;
                while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
                    bos.write(buffer, 0, len1);
                }

                byte[] var7 = bos.toByteArray();
                return var7;
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException var14) {
                    var14.printStackTrace();
                }

                bos.close();
            }
        }
    }
}


/*
 * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
 */
package com.baidu.ai.aip.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;

import java.lang.reflect.Type;

/**
 * Json宸ュ叿绫�.
 */
public class GsonUtils {
    private static Gson gson = new GsonBuilder().create();

    public static String toJson(Object value) {
        return gson.toJson(value);
    }

    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException {
        return gson.fromJson(json, classOfT);
    }

    public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
        return (T) gson.fromJson(json, typeOfT);
    }
}

package com.baidu.ai.aip.utils;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

/**
 * http 宸ュ叿绫�
 */
public class HttpUtil {

    public static String post(String requestUrl, String accessToken, String params)
            throws Exception {
        String contentType = "application/x-www-form-urlencoded";
        return HttpUtil.post(requestUrl, accessToken, contentType, params);
    }

    public static String post(String requestUrl, String accessToken, String contentType, String params)
            throws Exception {
        String encoding = "UTF-8";
        if (requestUrl.contains("nlp")) {
            encoding = "GBK";
        }
        return HttpUtil.post(requestUrl, accessToken, contentType, params, encoding);
    }

    public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding)
            throws Exception {
        String url = requestUrl + "?access_token=" + accessToken;
        return HttpUtil.postGeneralUrl(url, contentType, params, encoding);
    }

    public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding)
            throws Exception {
        URL url = new URL(generalUrl);
        // 鎵撳紑鍜孶RL涔嬮棿鐨勮繛鎺�
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        // 璁剧疆閫氱敤鐨勮姹傚睘鎬�
        connection.setRequestProperty("Content-Type", contentType);
        connection.setRequestProperty("Connection", "Keep-Alive");
        connection.setUseCaches(false);
        connection.setDoOutput(true);
        connection.setDoInput(true);

        // 寰楀埌璇锋眰鐨勮緭鍑烘祦瀵硅薄
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
        out.write(params.getBytes(encoding));
        out.flush();
        out.close();

        // 寤虹珛瀹為檯鐨勮繛鎺�
        connection.connect();
        // 鑾峰彇鎵�鏈夊搷搴斿ご瀛楁
        Map<String, List<String>> headers = connection.getHeaderFields();
        // 閬嶅巻鎵�鏈夌殑鍝嶅簲澶村瓧娈�
        for (String key : headers.keySet()) {
            System.err.println(key + "--->" + headers.get(key));
        }
        // 瀹氫箟 BufferedReader杈撳叆娴佹潵璇诲彇URL鐨勫搷搴�
        BufferedReader in = null;
        in = new BufferedReader(
                new InputStreamReader(connection.getInputStream(), encoding));
        String result = "";
        String getLine;
        while ((getLine = in.readLine()) != null) {
            result += getLine;
        }
        in.close();
        System.err.println("result:" + result);
        return result;
    }
}
    

 

我们所做的就是按百度的格式给她两张图片,然后他就给你结果。我们所做的只此而已。

 

6.我们如何实现登录注册呢。

 

先在我们的应用里建个人脸库。

 

package com.lb.service;

import com.baidu.ai.aip.utils.HttpUtil;
import com.lb.token.Token;
import com.baidu.ai.aip.utils.Base64Util;
import com.baidu.ai.aip.utils.FileUtil;
import com.baidu.ai.aip.utils.GsonUtils;

import java.io.IOException;
import java.util.*;

/**
* 人脸注册
*/
public class FaceAdd {

    /**
    * 重要提示代码中所需工具类
    * FileUtil,Base64Util,HttpUtil,GsonUtils请从
    * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
    * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
    * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
    * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
    * 下载
    */
    public static String add(String image,String user_id,String user_info) {
        // 请求url
        String url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add";
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("image", image);
            map.put("group_id", Token.userGroup);
            map.put("user_id", user_id);
            map.put("user_info", user_info);
            map.put("liveness_control", "NORMAL");
            map.put("image_type", "BASE64");
            map.put("quality_control", "LOW");

            String param = GsonUtils.toJson(map);

            // 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
            String accessToken = Token.token;

            String result = HttpUtil.post(url, accessToken, "application/json", param);
            System.out.println(result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
    	byte[] bytes1;
		try {
			bytes1 = FileUtil.readFileByBytes("E:\\java02\\faceTest\\WebContent\\photo\\lanbing.jpg");
			String image1 = Base64Util.encode(bytes1);
			FaceAdd.add(image1,"18897829387","兰兵");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
 
    }
}

 

7.人脸搜索

 

package com.lb.service;

import com.baidu.ai.aip.utils.HttpUtil;
import com.google.gson.Gson;
import com.lb.opj.MsgSearch;
import com.lb.token.Token;
import com.baidu.ai.aip.utils.Base64Util;
import com.baidu.ai.aip.utils.FileUtil;
import com.baidu.ai.aip.utils.GsonUtils;

import java.io.IOException;
import java.util.*;

/**
* 人脸搜索
*/
public class FaceSearch {

    /**
    * 重要提示代码中所需工具类
    * FileUtil,Base64Util,HttpUtil,GsonUtils请从
    * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
    * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
    * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
    * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
    * 下载
    */
    public static String search(String image) {
        // 请求url
        String url = "https://aip.baidubce.com/rest/2.0/face/v3/search";
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("image", image);
            map.put("liveness_control", "NORMAL");
            map.put("group_id_list", Token.userGroup);
            map.put("image_type", "BASE64");
            map.put("quality_control", "LOW");

            String param = GsonUtils.toJson(map);

            // 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
            String accessToken = Token.token;
            
            String result = HttpUtil.post(url, accessToken, "application/json", param);
            System.out.println(result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String Search(byte[] bytes1) {
    	
		String image1 = Base64Util.encode(bytes1);
		Gson g = new Gson();
		MsgSearch msg = g.fromJson(FaceSearch.search(image1), MsgSearch.class);
		return g.toJson(msg.ShowSearched());
        
    }
}

 

 

以上是后台的代码,我们只需传图片到后台调用即可。

register.html

<!DOCTYPE html>
<html lang="ZH-CN">
<head>
<meta charset="utf-8">
<title>face 测试</title>
<style>
#video {
	border: 1px solid #ddd;
}

.booth {
	position: relative;
}

.picLine {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	top: 0px;
	left: 0px;
}

.vid {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	top: 0px;
	left: 0px;
	z-index: 99;
}

.screencapture {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	left: 500px;
}

.info {
	position: absolute;
	top: 350px;
}
</style>

<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
</head>
<body>
	<div class="booth">

		<div class="vid">
			<video id="video" width="400" height="300"></video>
		</div>
	</div>

	<div>
		<div class="screencapture">
			<canvas id='canvas' width='400' height='300'></canvas>
		</div>
	</div>
	<div class="info">
		<label>账号:</label> <input type="text" name="account" id="account">
		<label>姓名:</label> <input type="text" name="name" id="name"> <br>
		<br> <br>
		<button id='tack'>注册</button>
	</div>



	<script>
    var video = document.getElementById('video'),
            canvas = document.getElementById('canvas'),
            snap = document.getElementById('tack'),
            img = document.getElementById('img'),
            vendorUrl = window.URL || window.webkitURL;

    //媒体对象
    navigator.getMedia = navigator.getUserMedia ||
            navagator.webkitGetUserMedia ||
            navigator.mozGetUserMedia ||
            navigator.msGetUserMedia;
    navigator.getMedia({
        video: true, //使用摄像头对象
        audio: false  //不适用音频
    }, function (strem) {
        console.log(strem);
        video.src = vendorUrl.createObjectURL(strem);
        video.play();
    }, function (error) {
        //error.code
        console.log(error);
    });
    snap.addEventListener('click', function () {

        //绘制canvas图形
        canvas.getContext('2d').drawImage(video, 0, 0, 400, 300);
        var saveImg = canvas.toDataURL('image/png');
        var account = document.getElementById("account").value;
        var name = document.getElementById("name").value;

        $.ajax({
            url: "Uploadpic",
            type: 'post',
            data: {"saveImg":saveImg.substring(22),"name":name,"account":account},
            success: function () {
                alert('保存成功');
            }
        });
    })
    

</script>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="ZH-CN">
<head>
<meta charset="utf-8">
<title>face 测试</title>
<style>
#video {
	border: 1px solid #ddd;
}

.booth {
	position: relative;
}

.picLine {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	top: 0px;
	left: 0px;
}

.vid {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	top: 0px;
	left: 0px;
	z-index: 99;
}

.screencapture {
	border: 1px solid #ddd;
	width: 400px;
	height: 300px;
	position: absolute;
	left: 500px;
}

.info {
	position: absolute;
	top: 350px;
}
</style>

<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
</head>
<body>
	<div class="booth">

		<div class="vid">
			<video id="video" width="400" height="300"></video>
		</div>
	</div>

	<div>
		<div class="screencapture">
			<canvas id='canvas' width='400' height='300'></canvas>
		</div>
	</div>
	<div class="info">
		<p id="name">adsfsf</p>
	</div>



	<script>
    var video = document.getElementById('video'),
            canvas = document.getElementById('canvas'),
            snap = document.getElementById('tack'),
            img = document.getElementById('img'),
            vendorUrl = window.URL || window.webkitURL;

    //媒体对象
    navigator.getMedia = navigator.getUserMedia ||
            navagator.webkitGetUserMedia ||
            navigator.mozGetUserMedia ||
            navigator.msGetUserMedia;
    navigator.getMedia({
        video: true, //使用摄像头对象
        audio: false  //不适用音频
    }, function (strem) {
        console.log(strem);
        video.src = vendorUrl.createObjectURL(strem);
        video.play();
    }, function (error) {
        //error.code
        console.log(error);
    });
    
    //截取图片并请求后台
    function login() {
    	var isok = false;
        //绘制canvas图形
        canvas.getContext('2d').drawImage(video, 0, 0, 400, 300);
        var saveImg = canvas.toDataURL('image/png');

        $.ajax({
            url: "Login",
            type: 'post',
            data: {"saveImg":saveImg.substring(22)},
            success: function (data) {
            	
                document.getElementById("name").innerHTML = data;
            	this.isok = true;
                console.info(data);
            }
        });
        
        return isok;
    }
    
    //隔一秒请求一次
    function search(){
    	var lo = login();
    	if(lo){
    		return 0;
    	}else{
    		setTimeout(search,1000);
    	}
    }
    
    window.onload=function (){
    	search();
    }

</script>
</body>
</html>

总结:我们所要做的就是获取用户图像信息,通过百度的接口传给她就OK了·。

 

完整代码:发邮件至1043624279@qq.com即可

C++实现的马赛克类

阅读数 2691

没有更多推荐了,返回首页