本文实现基于eigenface的人脸检测与识别。给定一个图像数据库,进行以下步骤:

  • 进行人脸检测,将检测出的人脸存入数据库2
  • 对数据库2进行人脸建模
  • 在测试集上进行recognition
 
本篇实现第一步:

  • 进行人脸检测,将检测出的人脸存入数据库2

环境:vs2010+opencv 2.4.6.0

特征:eigenface

Input:一个人脸数据库,15个人,每人20个样本(左右)。

Output:人脸检测,并识别出每张检测到的人脸。

===============================

本文完成第一步,数据预处理:自动检测所有文件夹中每个sample中的人脸,作为训练数据。

Input:一个color文件夹,每个文件夹中有1~N这N个子文件夹,每个子文件夹内有n张包括第n类人的照片,如图。

最终结果:

核心:face detection(detectAndDraw)

辅助:截图并保存部分图片(CutImg),文件夹内图片遍历(read_img),图片转换成相同大小(normalizeone)

括号内分别是函数名,下面分别给出代码及说明。

1. 遍历文件夹:CBrowseDir类和CStatDir类(具体见这篇),三个文件如下:

1.1 BrowseDir.h

  1. #pragma once
  2. #include "direct.h"
  3. #include "string.h"
  4. #include "io.h"
  5. #include "stdio.h"
  6. #include <vector>
  7. #include <iostream>
  8. using namespace std;
  9. class CBrowseDir
  10. {
  11. protected:
  12. char m_szInitDir[_MAX_PATH];
  13. public:
  14. CBrowseDir();
  15. bool SetInitDir(const char *dir);
  16. bool BeginBrowse(const char *filespec);
  17. vector<char*> BeginBrowseFilenames(const char *filespec);
  18. protected:
  19. bool BrowseDir(const char *dir,const char *filespec);
  20. vector<char*> GetDirFilenames(const char *dir,const char *filespec);
  21. virtual bool ProcessFile(const char *filename);
  22. virtual void ProcessDir(const char *currentdir,const char *parentdir);
  23. };

1.2 BrowseDir.cpp

  1. #include "BrowseDir.h"
  2. #include "direct.h"
  3. #include "string.h"
  4. #include "io.h"
  5. #include "stdio.h"
  6. #include <vector>
  7. #include <iostream>
  8. using namespace std;
  9. CBrowseDir::CBrowseDir()
  10. {
  11. getcwd(m_szInitDir,_MAX_PATH);
  12. int len=strlen(m_szInitDir);
  13. if (m_szInitDir[len-1] != '\\')
  14. strcat(m_szInitDir,"\\");
  15. }
  16. bool CBrowseDir::SetInitDir(const char *dir)
  17. {
  18. if (_fullpath(m_szInitDir,dir,_MAX_PATH) == NULL)
  19. return false;
  20. if (_chdir(m_szInitDir) != 0)
  21. return false;
  22. int len=strlen(m_szInitDir);
  23. if (m_szInitDir[len-1] != '\\')
  24. strcat(m_szInitDir,"\\");
  25. return true;
  26. }
  27. vector<char*>CBrowseDir:: BeginBrowseFilenames(const char *filespec)
  28. {
  29. ProcessDir(m_szInitDir,NULL);
  30. return GetDirFilenames(m_szInitDir,filespec);
  31. }
  32. bool CBrowseDir::BeginBrowse(const char *filespec)
  33. {
  34. ProcessDir(m_szInitDir,NULL);
  35. return BrowseDir(m_szInitDir,filespec);
  36. }
  37. bool CBrowseDir::BrowseDir(const char *dir,const char *filespec)
  38. {
  39. _chdir(dir);
  40. long hFile;
  41. _finddata_t fileinfo;
  42. if ((hFile=_findfirst(filespec,&fileinfo)) != -1)
  43. {
  44. do
  45. {
  46. if (!(fileinfo.attrib & _A_SUBDIR))
  47. {
  48. char filename[_MAX_PATH];
  49. strcpy(filename,dir);
  50. strcat(filename,fileinfo.name);
  51. cout << filename << endl;
  52. if (!ProcessFile(filename))
  53. return false;
  54. }
  55. } while (_findnext(hFile,&fileinfo) == 0);
  56. _findclose(hFile);
  57. }
  58. _chdir(dir);
  59. if ((hFile=_findfirst("*.*",&fileinfo)) != -1)
  60. {
  61. do
  62. {
  63. if ((fileinfo.attrib & _A_SUBDIR))
  64. {
  65. if (strcmp(fileinfo.name,".") != 0 && strcmp
  66. (fileinfo.name,"..") != 0)
  67. {
  68. char subdir[_MAX_PATH];
  69. strcpy(subdir,dir);
  70. strcat(subdir,fileinfo.name);
  71. strcat(subdir,"\\");
  72. ProcessDir(subdir,dir);
  73. if (!BrowseDir(subdir,filespec))
  74. return false;
  75. }
  76. }
  77. } while (_findnext(hFile,&fileinfo) == 0);
  78. _findclose(hFile);
  79. }
  80. return true;
  81. }
  82. vector<char*> CBrowseDir::GetDirFilenames(const char *dir,const char *filespec)
  83. {
  84. _chdir(dir);
  85. vector<char*>filename_vec;
  86. filename_vec.clear();
  87. long hFile;
  88. _finddata_t fileinfo;
  89. if ((hFile=_findfirst(filespec,&fileinfo)) != -1)
  90. {
  91. do
  92. {
  93. if (!(fileinfo.attrib & _A_SUBDIR))
  94. {
  95. char *filename = new char[_MAX_PATH];
  96. strcpy(filename,dir);
  97. //int st = 0;   while (dir[st++]!='\0');
  98. strcat(filename,fileinfo.name); //filename[st]='\0';
  99. filename_vec.push_back(filename);
  100. }
  101. } while (_findnext(hFile,&fileinfo) == 0);
  102. _findclose(hFile);
  103. }
  104. _chdir(dir);
  105. if ((hFile=_findfirst("*.*",&fileinfo)) != -1)
  106. {
  107. do
  108. {
  109. if ((fileinfo.attrib & _A_SUBDIR))
  110. {
  111. if (strcmp(fileinfo.name,".") != 0 && strcmp
  112. (fileinfo.name,"..") != 0)
  113. {
  114. char subdir[_MAX_PATH];
  115. strcpy(subdir,dir);
  116. strcat(subdir,fileinfo.name);
  117. strcat(subdir,"\\");
  118. ProcessDir(subdir,dir);
  119. return GetDirFilenames(subdir,filespec);
  120. }
  121. }
  122. } while (_findnext(hFile,&fileinfo) == 0);
  123. _findclose(hFile);
  124. }
  125. return filename_vec;
  126. }
  127. bool CBrowseDir::ProcessFile(const char *filename)
  128. {
  129. return true;
  130. }
  131. void CBrowseDir::ProcessDir(const char
  132. *currentdir,const char *parentdir)
  133. {
  134. }

1.3 StatDir.h

  1. #pragma once
  2. #include "browsedir.h"
  3. class CStatDir:public CBrowseDir
  4. {
  5. protected:
  6. int m_nFileCount;   //保存文件个数
  7. int m_nSubdirCount; //保存子目录个数
  8. public:
  9. CStatDir()
  10. {
  11. m_nFileCount=m_nSubdirCount=0;
  12. }
  13. int GetFileCount()
  14. {
  15. return m_nFileCount;
  16. }
  17. int GetSubdirCount()
  18. {
  19. return m_nSubdirCount-1;
  20. }
  21. protected:
  22. virtual bool ProcessFile(const char *filename)
  23. {
  24. m_nFileCount++;
  25. return CBrowseDir::ProcessFile(filename);
  26. }
  27. virtual void ProcessDir
  28. (const char *currentdir,const char *parentdir)
  29. {
  30. m_nSubdirCount++;
  31. CBrowseDir::ProcessDir(currentdir,parentdir);
  32. }
  33. };

2. 辅助函数Prehelper.h, Prehelper.cpp:负责返回文件夹内所有图片(read_img),检测人脸(detectAndDraw并可以在原图中画出),截图(CutImg),提取(DetectandExtract)

2.1 Prehelper.h

  1. //preprocessing helper
  2. //@ Author : Rachel-Zhang
  3. #include "opencv2/core/core.hpp"
  4. #include "opencv2/highgui/highgui.hpp"
  5. #include "opencv2/contrib/contrib.hpp"
  6. #include <cv.h>
  7. #include <vector>
  8. #include <utility>
  9. using namespace cv;
  10. using namespace std;
  11. void normalizeone(const char* dir,IplImage* standard);
  12. void CutImg(IplImage* src, CvRect rect,IplImage* res);
  13. vector<Rect> detectAndDraw( Mat& img, CascadeClassifier& cascade,
  14. CascadeClassifier& nestedCascade,
  15. double scale, bool tryflip,bool draw );
  16. IplImage* DetectandExtract(Mat& img, CascadeClassifier& cascade,
  17. CascadeClassifier& nestedCascade,
  18. double scale, bool tryflip);
  19. int read_img(const string& dir, vector<Mat> &images);
  20. vector<pair<char*,Mat>>  read_img(const string& dir);

2.2 Prehelper.cpp

  1. #include "Prehelper.h"
  2. #include "BrowseDir.h"
  3. #include "StatDir.h"
  4. #include <opencv2/core/core.hpp>
  5. #include <opencv2/highgui/highgui.hpp>
  6. #include <cv.h>
  7. using namespace cv;
  8. void normalizeone(const char* dir,IplImage* standard)
  9. {
  10. CStatDir statdir;
  11. if (!statdir.SetInitDir(dir))
  12. {
  13. puts("Dir not exist");
  14. return;
  15. }
  16. vector<char*>file_vec = statdir.BeginBrowseFilenames("*.*");
  17. int i;
  18. for (i=0;i<file_vec.size();i++)
  19. {
  20. IplImage* cur_img = cvLoadImage(file_vec[i],CV_LOAD_IMAGE_GRAYSCALE);
  21. //IplImage*cur_gray = cvCreateImage(cvGetSize(cur_img),cur_img->depth,1);
  22. cvResize(cur_img,standard,CV_INTER_AREA);
  23. //cvCvtColor(standard,cur_gray,CV_RGB2GRAY);
  24. //      cvNamedWindow("cur_img",CV_WINDOW_AUTOSIZE);
  25. //      cvNamedWindow("standard",CV_WINDOW_AUTOSIZE);
  26. //      cvShowImage("cur_img",cur_img);
  27. //      cvShowImage("standard",standard);
  28. //      cvWaitKey();
  29. cvSaveImage(file_vec[i],cur_img);
  30. }
  31. }
  32. void CutImg(IplImage* src, CvRect rect,IplImage* res)
  33. {
  34. CvSize imgsize;
  35. imgsize.height = rect.height;
  36. imgsize.width = rect.width;
  37. cvSetImageROI(src,rect);
  38. cvCopy(src,res);
  39. cvResetImageROI(res);
  40. }
  41. int read_img(const string& dir, vector<Mat> &images)
  42. {
  43. CStatDir statdir;
  44. if (!statdir.SetInitDir(dir.c_str()))
  45. {
  46. cout<<"Direct "<<dir<<"  not exist!"<<endl;
  47. return 0;
  48. }
  49. int cls_id = dir[dir.length()-1]-'0';
  50. vector<char*>file_vec = statdir.BeginBrowseFilenames("*.*");
  51. int i,s = file_vec.size();
  52. for (i=0;i<s;i++)
  53. {
  54. Mat graymat = imread(file_vec[i],0);
  55. //graymat.reshape(1,1);//flatten to one row
  56. images.push_back(graymat);
  57. }
  58. return s;
  59. }
  60. vector<pair<char*,Mat>>  read_img(const string& dir)
  61. {
  62. CStatDir statdir;
  63. pair<char*,Mat> pfi;
  64. vector<pair<char*,Mat>> Vp;
  65. if (!statdir.SetInitDir(dir.c_str()))
  66. {
  67. cout<<"Direct "<<dir<<"  not exist!"<<endl;
  68. return Vp;
  69. }
  70. int cls_id = dir[dir.length()-1]-'0';
  71. vector<char*>file_vec = statdir.BeginBrowseFilenames("*.*");
  72. int i,s = file_vec.size();
  73. for (i=0;i<s;i++)
  74. {
  75. pfi.first = file_vec[i];
  76. pfi.second = imread(file_vec[i]);
  77. Vp.push_back(pfi);
  78. }
  79. return Vp;
  80. }
  81. vector<Rect> detectAndDraw( Mat& img, CascadeClassifier& cascade,
  82. CascadeClassifier& nestedCascade,
  83. double scale, bool tryflip, bool draw )
  84. {
  85. int i = 0;
  86. double t = 0;
  87. vector<Rect> faces, faces2;
  88. const static Scalar colors[] =  { CV_RGB(0,0,255),
  89. CV_RGB(0,128,255),
  90. CV_RGB(0,255,255),
  91. CV_RGB(0,255,0),
  92. CV_RGB(255,128,0),
  93. CV_RGB(255,255,0),
  94. CV_RGB(255,0,0),
  95. CV_RGB(255,0,255)} ;
  96. Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 );
  97. cvtColor( img, gray, CV_BGR2GRAY );
  98. resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR );
  99. equalizeHist( smallImg, smallImg );
  100. t = (double)cvGetTickCount();
  101. cascade.detectMultiScale( smallImg, faces,
  102. 1.1, 2, 0
  103. |CV_HAAR_FIND_BIGGEST_OBJECT
  104. //|CV_HAAR_DO_ROUGH_SEARCH
  105. //|CV_HAAR_SCALE_IMAGE
  106. ,
  107. Size(30, 30) );
  108. if( tryflip )
  109. {
  110. flip(smallImg, smallImg, 1);
  111. cascade.detectMultiScale( smallImg, faces2,
  112. 1.1, 2, 0
  113. |CV_HAAR_FIND_BIGGEST_OBJECT
  114. //|CV_HAAR_DO_ROUGH_SEARCH
  115. //|CV_HAAR_SCALE_IMAGE
  116. ,
  117. Size(30, 30) );
  118. for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); r++ )
  119. {
  120. faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
  121. }
  122. }
  123. t = (double)cvGetTickCount() - t;
  124. printf( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) );
  125. if(draw)
  126. {
  127. for( vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++ )
  128. {
  129. Mat smallImgROI;
  130. vector<Rect> nestedObjects;
  131. Point center;
  132. Scalar color = colors[i%8];
  133. int radius;
  134. double aspect_ratio = (double)r->width/r->height;
  135. rectangle( img, cvPoint(cvRound(r->x*scale), cvRound(r->y*scale)),
  136. cvPoint(cvRound((r->x + r->width-1)*scale), cvRound((r->y + r->height-1)*scale)),
  137. color, 3, 8, 0);
  138. if( nestedCascade.empty() )
  139. continue;
  140. smallImgROI = smallImg(*r);
  141. nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
  142. 1.1, 2, 0
  143. |CV_HAAR_FIND_BIGGEST_OBJECT
  144. //|CV_HAAR_DO_ROUGH_SEARCH
  145. //|CV_HAAR_DO_CANNY_PRUNING
  146. //|CV_HAAR_SCALE_IMAGE
  147. ,
  148. Size(30, 30) );
  149. //draw eyes
  150. //         for( vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++ )
  151. //         {
  152. //             center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);
  153. //             center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);
  154. //             radius = cvRound((nr->width + nr->height)*0.25*scale);
  155. //             circle( img, center, radius, color, 3, 8, 0 );
  156. //         }
  157. }
  158. cv::imshow( "result", img );
  159. }
  160. return faces;
  161. }
  162. IplImage* DetectandExtract(Mat& img, CascadeClassifier& cascade,
  163. CascadeClassifier& nestedCascade,
  164. double scale, bool tryflip)
  165. {
  166. vector<Rect> Rvec = detectAndDraw(img,cascade,nestedCascade,scale,tryflip,0);
  167. int i,maxxsize=0,id=-1,area;
  168. for (i=0;i<Rvec.size();i++)
  169. {
  170. area = Rvec[i].width*Rvec[i].height;
  171. if(maxxsize<area)
  172. {
  173. maxxsize = area;
  174. id = i;
  175. }
  176. }
  177. IplImage* transimg = cvCloneImage(&(IplImage)img);
  178. if(id!=-1)
  179. {
  180. CvSize imgsize;
  181. imgsize.height = Rvec[id].height;
  182. imgsize.width = Rvec[id].width;
  183. IplImage* res = cvCreateImage(imgsize,transimg->depth,transimg->nChannels);
  184. CutImg(transimg,Rvec[id],res);
  185. return res;
  186. }
  187. return NULL;
  188. }

3. 主函数

  1. //Detect.cpp
  2. //Preprocessing - Detect, Cut and Save
  3. //@Author : Rachel-Zhang
  4. #include "opencv2/objdetect/objdetect.hpp"
  5. #include "opencv2/highgui/highgui.hpp"
  6. #include "opencv2/imgproc/imgproc.hpp"
  7. #include <cctype>
  8. #include <iostream>
  9. #include <iterator>
  10. #include <stdio.h>
  11. #include "BrowseDir.h"
  12. #include "StatDir.h"
  13. #include "Prehelper.h"
  14. using namespace std;
  15. using namespace cv;
  16. #define CAM 2
  17. #define PHO 1
  18. #define K 5
  19. string cascadeName = "E:/software/opencv2.4.6.0/data/haarcascades/haarcascade_frontalface_alt.xml";
  20. string nestedCascadeName = "E:/software/opencv2.4.6.0/data/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
  21. int main( )
  22. {
  23. CvCapture* capture = 0;
  24. Mat frame, frameCopy, image;
  25. string inputName;
  26. bool tryflip = false;
  27. int mode;
  28. CascadeClassifier cascade, nestedCascade;
  29. double scale = 1.0;
  30. if( !cascade.load( cascadeName ) ||!nestedCascade.load( nestedCascadeName))
  31. {
  32. cerr << "ERROR: Could not load classifier cascade or nestedCascade" << endl;//若出现该问题请去检查cascadeName,可能是opencv版本路径问题
  33. return -1;
  34. }
  35. //  printf("select the mode of detection: \n1: from picture\t 2: from camera\n");
  36. //  scanf("%d",&mode);
  37. char** pics = (char**) malloc(sizeof*pics);
  38. /************************************************************************/
  39. /*                                  detect face and save                                    */
  40. /************************************************************************/
  41. int i,j;
  42. cout<<"detect and save..."<<endl;
  43. const char dir[256] = "D:\\Face_recognition\\pic\\";
  44. string cur_dir;
  45. char id[5];
  46. for(i=1; i<=K; i++)
  47. {
  48. cur_dir = dir;
  49. _itoa(i,id,10);
  50. cur_dir.append("color\\");
  51. cur_dir.append(id);
  52. vector<pair<char*,Mat>> imgs=read_img(cur_dir);
  53. for(j=0;j<imgs.size();j++)
  54. {
  55. IplImage* res = DetectandExtract(imgs[j].second,cascade,nestedCascade,scale,tryflip);
  56. if(res)
  57. cvSaveImage(imgs[j].first,res);
  58. }
  59. }
  60. return 0;
  61. }

正确的输出就是一系列人脸检测时间,且原文件夹内的图片变成了检测出的人脸(如上面结果图所示)。

文章所用代码打包链接:http://download.csdn.net/detail/abcjennifer/7047853

from: http://blog.csdn.net/abcjennifer/article/details/20396869

opencv 人脸识别 (一)训练样本的处理的更多相关文章

  1. OpenCV人脸识别的原理 .

    OpenCV人脸识别的原理 . 在之前讲到的人脸测试后,提取出人脸来,并且保存下来,以供训练或识别是用,提取人脸的代码如下: void GetImageRect(IplImage* orgImage, ...

  2. opencv人脸识别代码

    opencv人脸识别C++代码 /* * Copyright (c) 2011,2012. Philipp Wagner <bytefish[at]gmx[dot]de>. * Relea ...

  3. opencv人脸识别提取手机相册内人物充当数据集,身份识别学习(草稿)

    未写完 采用C++,opencv+opencv contrib 4.1.0 对手机相册内人物opencv人脸识别,身份识别学习 最近事情多,介绍就先不介绍了 photocut.c #include & ...

  4. OpenCV人脸识别Eigen算法源码分析

    1 理论基础 学习Eigen人脸识别算法需要了解一下它用到的几个理论基础,现总结如下: 1.1 协方差矩阵 首先需要了解一下公式: 共公式可以看出:均值描述的是样本集合的平均值,而标准差描述的则是样本 ...

  5. OpenCV人脸识别LBPH算法源码分析

    1 背景及理论基础 人脸识别是指将一个需要识别的人脸和人脸库中的某个人脸对应起来(类似于指纹识别),目的是完成识别功能,该术语需要和人脸检测进行区分,人脸检测是在一张图片中把人脸定位出来,完成的是搜寻 ...

  6. opencv 人脸识别

      背景知识 OpenCV 是一个开源的计算机视觉和机器学习库.它包含成千上万优化过的算法,为各种计算机视觉应用提供了一个通用工具包.根据这个项目的关于页面,OpenCV 已被广泛运用在各种项目上,从 ...

  7. OpenCV 人脸识别 C++实例代码

    #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include & ...

  8. opencv 人脸识别 (二)训练和识别

    上一篇中我们对训练数据做了一些预处理,检测出人脸并保存在\pic\color\x文件夹下(x=1,2,3,...类别号),本文做训练和识别.为了识别,首先将人脸训练数据 转为灰度.对齐.归一化,再放入 ...

  9. OpenCv 人脸识别 基础

    #include <opencv2\opencv.hpp> #include <iostream> using namespace std; int main() { // 摄 ...

随机推荐

  1. C#制作高仿360安全卫士窗体(四)- 水晶按钮

    项目越来越紧,我也乐此不疲.自从上次C#制作高仿360安全卫士窗体(三)出来之后,就开始有一些人在说为什么还在坚持写这么落后的东西.我想说的是,我是从事企业信息化工作的,所有程序都只对内部使用.所以只 ...

  2. SASS学习笔记_01

      scss两种格式 sass    大括号 scss   css写法   Arguments:  --no-cache –style compressed --update $FileName$:c ...

  3. ASP.NET MVC学习之视图篇(1)

    一.前言 不知道还有多少读者从第一篇开始一直学习到如今,笔者也会一直坚持将ASP.NET MVC的学习完美的结束掉,然后开始写如何配合其他框架使用ASP.NET MVC的随笔.当然笔者后面的随笔如果没 ...

  4. bzoj 1800 暴力枚举

    直接暴力枚举四个点,然后判断是否能组成矩形就行了 注意枚举的点的标号从小到大,保证不重复枚举 /**************************************************** ...

  5. Poj 1255 覆盖的面积 2014-07-28 12:29 116人阅读 评论(0) 收藏

    覆盖的面积 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  6. yebis 和phyreengine的集成

    被虐了几个礼拜阿, 暗无天日阿,花样被虐阿 设置 backbuffer commandbuffer这种问题还在其次,和他们的support要phyreengine 的sample就可以了 虐我千百遍的 ...

  7. .Net 执行 Oracle SQL语句时, 中文变问号

      带中文的Sql语句在.Net调用时, 中文变问号(可使用 SQL Tracker工具跟踪)   问题:       服务器的字符集与客户端的字符集不一致. 解决方法: 1.  查看服务端的字符集: ...

  8. Spring声明式事务配置管理方法(转)

    项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add libra ...

  9. [HTML/CSS]display:none和visibility:hidden的区别

    写在前面 在群里有朋友问这样一个问题,display:none的标签,影响了布局.这就引出了本篇这样的问题,印象中display:none的块元素是不占位置的. 一个例子 <!DOCTYPE h ...

  10. Android 4.4 KitKat, the browser and the Chrome WebView

    Having V8 as the JavaScript engine for the new web view, the JavaScript performance if much better, ...