介绍基于皮肤检测之后的,寻找最大连通区域,完成脸谱检测的算法。大致的算法步骤如下:

原图如下:

每步处理以后的效果:

程序运行,加载选择图像以后的截屏如下:

截屏中显示图片,是适当放缩以后,代码如下:

[java] view plaincopy
  1. Image scaledImage = rawImg.getScaledInstance(200, 200, Image.SCALE_FAST); // Java Image API, rawImage is source image
  2. g2.drawImage(scaledImage, 0, 0, 200, 200, null);

第一步:图像预处理,预处理的目的是为了减少图像中干扰像素,使得皮肤检测步骤可以得

到更好的效果,最常见的手段是调节对比度与亮度,也可以高斯模糊。

这里调节对比度的算法很简单,源代码如下:

[java] view plaincopy
  1. package com.gloomyfish.face.detection;
  2. import java.awt.image.BufferedImage;
  3. public class ContrastFilter extends AbstractBufferedImageOp {
  4. private double nContrast = 30;
  5. public ContrastFilter() {
  6. System.out.println("Contrast Filter");
  7. }
  8. @Override
  9. public BufferedImage filter(BufferedImage src, BufferedImage dest) {
  10. int width = src.getWidth();
  11. int height = src.getHeight();
  12. double contrast = (100.0 + nContrast) / 100.0;
  13. contrast *= contrast;
  14. if ( dest == null )
  15. dest = createCompatibleDestImage( src, null );
  16. int[] inPixels = new int[width*height];
  17. int[] outPixels = new int[width*height];
  18. getRGB( src, 0, 0, width, height, inPixels );
  19. int index = 0;
  20. int ta = 0, tr = 0, tg = 0, tb = 0;
  21. for(int row=0; row<height; row++) {
  22. for(int col=0; col<width; col++) {
  23. index = row * width + col;
  24. ta = (inPixels[index] >> 24) & 0xff;
  25. tr = (inPixels[index] >> 16) & 0xff;
  26. tg = (inPixels[index] >> 8) & 0xff;
  27. tb = inPixels[index] & 0xff;
  28. // adjust contrast - red, green, blue
  29. tr = adjustContrast(tr, contrast);
  30. tg = adjustContrast(tg, contrast);
  31. tb = adjustContrast(tb, contrast);
  32. // output RGB pixel
  33. outPixels[index] = (ta << 24) | (tr << 16) | (tg << 8) | tb;
  34. }
  35. }
  36. setRGB( dest, 0, 0, width, height, outPixels );
  37. return dest;
  38. }
  39. public int adjustContrast(int color, double contrast) {
  40. double result = 0;
  41. result = color / 255.0;
  42. result -= 0.5;
  43. result *= contrast;
  44. result += 0.5;
  45. result *=255.0;
  46. return clamp((int)result);
  47. }
  48. public static int clamp(int c) {
  49. if (c < 0)
  50. return 0;
  51. if (c > 255)
  52. return 255;
  53. return c;
  54. }
  55. }

注意:第一步不是必须的,如果图像质量已经很好,可以直接跳过。


第二步:皮肤检测,采用的是基于RGB色彩空间的统计结果来判断一个像素是否为skin像

素,如果是皮肤像素,则设置像素为黑色,否则为白色。给出基于RGB色彩空间的五种皮

肤检测统计方法,最喜欢的一种源代码如下:

[java] view plaincopy
  1. package com.gloomyfish.face.detection;
  2. import java.awt.image.BufferedImage;
  3. /**
  4. * this skin detection is absolutely good skin classification,
  5. * i love this one very much
  6. *
  7. * this one should be always primary skin detection
  8. * from all five filters
  9. *
  10. * @author zhigang
  11. *
  12. */
  13. public class SkinFilter4 extends AbstractBufferedImageOp {
  14. @Override
  15. public BufferedImage filter(BufferedImage src, BufferedImage dest) {
  16. int width = src.getWidth();
  17. int height = src.getHeight();
  18. if ( dest == null )
  19. dest = createCompatibleDestImage( src, null );
  20. int[] inPixels = new int[width*height];
  21. int[] outPixels = new int[width*height];
  22. getRGB( src, 0, 0, width, height, inPixels );
  23. int index = 0;
  24. for(int row=0; row<height; row++) {
  25. int ta = 0, tr = 0, tg = 0, tb = 0;
  26. for(int col=0; col<width; col++) {
  27. index = row * width + col;
  28. ta = (inPixels[index] >> 24) & 0xff;
  29. tr = (inPixels[index] >> 16) & 0xff;
  30. tg = (inPixels[index] >> 8) & 0xff;
  31. tb = inPixels[index] & 0xff;
  32. // detect skin method...
  33. double sum = tr + tg + tb;
  34. if (((double)tb/(double)tg<1.249) &&
  35. ((double)sum/(double)(3*tr)>0.696) &&
  36. (0.3333-(double)tb/(double)sum>0.014) &&
  37. ((double)tg/(double)(3*sum)<0.108))
  38. {
  39. tr = tg = tb = 0;
  40. } else {
  41. tr = tg = tb = 255;
  42. }
  43. outPixels[index] = (ta << 24) | (tr << 16) | (tg << 8) | tb;
  44. }
  45. }
  46. setRGB(dest, 0, 0, width, height, outPixels);
  47. return dest;
  48. }
  49. }

第三步:寻找最大连通区域

使用连通组件标记算法,寻找最大连通区域,关于什么是连通组件标记算法,可以参见这里

http://blog.csdn.net/jia20003/article/details/7483249,里面提到的连通组件算法效率不高,所

以这里我完成了一个更具效率的版本,主要思想是对像素数据进行八邻域寻找连通,然后合

并标记。源代码如下:

[java] view plaincopy
  1. package com.gloomyfish.face.detection;
  2. import java.util.Arrays;
  3. import java.util.HashMap;
  4. /**
  5. * fast connected component label algorithm
  6. *
  7. * @date 2012-05-23
  8. * @author zhigang
  9. *
  10. */
  11. public class FastConnectedComponentLabelAlg {
  12. private int bgColor;
  13. private int[] labels;
  14. private int[] outData;
  15. private int dw;
  16. private int dh;
  17. public FastConnectedComponentLabelAlg() {
  18. bgColor = 255; // black color
  19. }
  20. public int[] doLabel(int[] inPixels, int width, int height) {
  21. dw = width;
  22. dh = height;
  23. int nextlabel = 1;
  24. int result = 0;
  25. labels = new int[dw * dh/2];
  26. outData = new int[dw * dh];
  27. for(int i=0; i<labels.length; i++) {
  28. labels[i] = i;
  29. }
  30. // we need to define these two variable arrays.
  31. int[] fourNeighborhoodPixels = new int[8];
  32. int[] fourNeighborhoodLabels = new int[8];
  33. int[] knownLabels = new int[4];
  34. int srcrgb = 0, index = 0;
  35. boolean existedLabel = false;
  36. for(int row = 0; row < height; row ++) {
  37. for(int col = 0; col < width; col++) {
  38. index = row * width + col;
  39. srcrgb = inPixels[index] & 0x000000ff;
  40. if(srcrgb == bgColor) {
  41. result = 0; // which means no labeled for this pixel.
  42. } else {
  43. // we just find the eight neighborhood pixels.
  44. fourNeighborhoodPixels[0] = getPixel(inPixels, row-1, col); // upper cell
  45. fourNeighborhoodPixels[1] = getPixel(inPixels, row, col-1); // left cell
  46. fourNeighborhoodPixels[2] = getPixel(inPixels, row+1, col); // bottom cell
  47. fourNeighborhoodPixels[3] = getPixel(inPixels, row, col+1); // right cell
  48. // four corners pixels
  49. fourNeighborhoodPixels[4] = getPixel(inPixels, row-1, col-1); // upper left corner
  50. fourNeighborhoodPixels[5] = getPixel(inPixels, row-1, col+1); // upper right corner
  51. fourNeighborhoodPixels[6] = getPixel(inPixels, row+1, col-1); // left bottom corner
  52. fourNeighborhoodPixels[7] = getPixel(inPixels, row+1, col+1); // right bottom corner
  53. // get current possible existed labels
  54. fourNeighborhoodLabels[0] = getLabel(outData, row-1, col); // upper cell
  55. fourNeighborhoodLabels[1] = getLabel(outData, row, col-1); // left cell
  56. fourNeighborhoodLabels[2] = getLabel(outData, row+1, col); // bottom cell
  57. fourNeighborhoodLabels[3] = getLabel(outData, row, col+1); // right cell
  58. // four corners labels value
  59. fourNeighborhoodLabels[4] = getLabel(outData, row-1, col-1); // upper left corner
  60. fourNeighborhoodLabels[5] = getLabel(outData, row-1, col+1); // upper right corner
  61. fourNeighborhoodLabels[6] = getLabel(outData, row+1, col-1); // left bottom corner
  62. fourNeighborhoodLabels[7] = getLabel(outData, row+1, col+1); // right bottom corner
  63. knownLabels[0] = fourNeighborhoodLabels[0];
  64. knownLabels[1] = fourNeighborhoodLabels[1];
  65. knownLabels[2] = fourNeighborhoodLabels[4];
  66. knownLabels[3] = fourNeighborhoodLabels[5];
  67. existedLabel = false;
  68. for(int k=0; k<fourNeighborhoodLabels.length; k++) {
  69. if(fourNeighborhoodLabels[k] != 0) {
  70. existedLabel = true;
  71. break;
  72. }
  73. }
  74. if(!existedLabel) {
  75. result = nextlabel;
  76. nextlabel++;
  77. } else {
  78. int found = -1, count = 0;
  79. for(int i=0; i<fourNeighborhoodPixels.length; i++) {
  80. if(fourNeighborhoodPixels[i] != bgColor) {
  81. found = i;
  82. count++;
  83. }
  84. }
  85. if(count == 1) {
  86. result = (fourNeighborhoodLabels[found] == 0) ? nextlabel : fourNeighborhoodLabels[found];
  87. } else {
  88. result = (fourNeighborhoodLabels[found] == 0) ? nextlabel : fourNeighborhoodLabels[found];
  89. for(int j=0; j<knownLabels.length; j++) {
  90. if(knownLabels[j] != 0 && knownLabels[j] != result &&
  91. knownLabels[j] < result) {
  92. result = knownLabels[j];
  93. }
  94. }
  95. boolean needMerge = false;
  96. for(int mm = 0; mm < knownLabels.length; mm++ ) {
  97. if(knownLabels[0] != knownLabels[mm] && knownLabels[mm] != 0) {
  98. needMerge = true;
  99. }
  100. }
  101. // merge the labels now....
  102. if(needMerge) {
  103. int minLabel = knownLabels[0];
  104. for(int m=0; m<knownLabels.length; m++) {
  105. if(minLabel > knownLabels[m] && knownLabels[m] != 0) {
  106. minLabel = knownLabels[m];
  107. }
  108. }
  109. // find the final label number...
  110. result = (minLabel == 0) ? result : minLabel;
  111. // re-assign the label number now...
  112. if(knownLabels[0] != 0) {
  113. setData(outData, row-1, col, result);
  114. }
  115. if(knownLabels[1] != 0) {
  116. setData(outData, row, col-1, result);
  117. }
  118. if(knownLabels[2] != 0) {
  119. setData(outData, row-1, col-1, result);
  120. }
  121. if(knownLabels[3] != 0) {
  122. setData(outData, row-1, col+1, result);
  123. }
  124. }
  125. }
  126. }
  127. }
  128. outData[index] = result; // assign to label
  129. }
  130. }
  131. // post merge each labels now
  132. for(int row = 0; row < height; row ++) {
  133. for(int col = 0; col < width; col++) {
  134. index = row * width + col;
  135. mergeLabels(index);
  136. }
  137. }
  138. // labels statistic
  139. HashMap<Integer, Integer> labelMap = new HashMap<Integer, Integer>();
  140. for(int d=0; d<outData.length; d++) {
  141. if(outData[d] != 0) {
  142. if(labelMap.containsKey(outData[d])) {
  143. Integer count = labelMap.get(outData[d]);
  144. count+=1;
  145. labelMap.put(outData[d], count);
  146. } else {
  147. labelMap.put(outData[d], 1);
  148. }
  149. }
  150. }
  151. // try to find the max connected component
  152. Integer[] keys = labelMap.keySet().toArray(new Integer[0]);
  153. Arrays.sort(keys);
  154. int maxKey = 1;
  155. int max = 0;
  156. for(Integer key : keys) {
  157. if(max < labelMap.get(key)){
  158. max = labelMap.get(key);
  159. maxKey = key;
  160. }
  161. System.out.println( "Number of " + key + " = " + labelMap.get(key));
  162. }
  163. System.out.println("maxkey = " + maxKey);
  164. System.out.println("max connected component number = " + max);
  165. return outData;
  166. }
  167. private void mergeLabels(int index) {
  168. int row = index / dw;
  169. int col = index % dw;
  170. // get current possible existed labels
  171. int min = getLabel(outData, row, col);
  172. if(min == 0) return;
  173. if(min > getLabel(outData, row-1, col) && getLabel(outData, row-1, col) != 0) {
  174. min = getLabel(outData, row-1, col);
  175. }
  176. if(min > getLabel(outData, row, col-1) && getLabel(outData, row, col-1) != 0) {
  177. min = getLabel(outData, row, col-1);
  178. }
  179. if(min > getLabel(outData, row+1, col) && getLabel(outData, row+1, col) != 0) {
  180. min = getLabel(outData, row+1, col);
  181. }
  182. if(min > getLabel(outData, row, col+1) && getLabel(outData, row, col+1) != 0) {
  183. min = getLabel(outData, row, col+1);
  184. }
  185. if(min > getLabel(outData, row-1, col-1) && getLabel(outData, row-1, col-1) != 0) {
  186. min = getLabel(outData, row-1, col-1);
  187. }
  188. if(min > getLabel(outData, row-1, col+1) && getLabel(outData, row-1, col+1) != 0) {
  189. min = getLabel(outData, row-1, col+1);
  190. }
  191. if(min > getLabel(outData, row+1, col-1) && getLabel(outData, row+1, col-1) != 0) {
  192. min = getLabel(outData, row+1, col-1);
  193. }
  194. if(min > getLabel(outData, row+1, col+1) && getLabel(outData, row+1, col+1) != 0) {
  195. min = getLabel(outData, row+1, col+1);
  196. }
  197. if(getLabel(outData, row, col) == min)
  198. return;
  199. outData[index] = min;
  200. // eight neighborhood pixels
  201. if((row -1) >= 0) {
  202. mergeLabels((row-1)*dw + col);
  203. }
  204. if((col-1) >= 0) {
  205. mergeLabels(row*dw+col-1);
  206. }
  207. if((row+1) < dh) {
  208. mergeLabels((row + 1)*dw+col);
  209. }
  210. if((col+1) < dw) {
  211. mergeLabels((row)*dw+col+1);
  212. }
  213. if((row-1)>= 0 && (col-1) >=0) {
  214. mergeLabels((row-1)*dw+col-1);
  215. }
  216. if((row-1)>= 0 && (col+1) < dw) {
  217. mergeLabels((row-1)*dw+col+1);
  218. }
  219. if((row+1) < dh && (col-1) >=0) {
  220. mergeLabels((row+1)*dw+col-1);
  221. }
  222. if((row+1) < dh && (col+1) < dw) {
  223. mergeLabels((row+1)*dw+col+1);
  224. }
  225. }
  226. private void setData(int[] data, int row, int col, int value) {
  227. if(row < 0 || row >= dh) {
  228. return;
  229. }
  230. if(col < 0 || col >= dw) {
  231. return;
  232. }
  233. int index = row * dw + col;
  234. data[index] = value;
  235. }
  236. private int getLabel(int[] data, int row, int col) {
  237. // handle the edge pixels
  238. if(row < 0 || row >= dh) {
  239. return 0;
  240. }
  241. if(col < 0 || col >= dw) {
  242. return 0;
  243. }
  244. int index = row * dw + col;
  245. return (data[index] & 0x000000ff);
  246. }
  247. private int getPixel(int[] data, int row, int col) {
  248. // handle the edge pixels
  249. if(row < 0 || row >= dh) {
  250. return bgColor;
  251. }
  252. if(col < 0 || col >= dw) {
  253. return bgColor;
  254. }
  255. int index = row * dw + col;
  256. return (data[index] & 0x000000ff);
  257. }
  258. /**
  259. * binary image data:
  260. *
  261. * 255, 0,   0,   255,   0,   255, 255, 0,   255, 255, 255,
  262. * 255, 0,   0,   255,   0,   255, 255, 0,   0,   255, 0,
  263. * 255, 0,   0,   0,     255, 255, 255, 255, 255, 0,   0,
  264. * 255, 255, 0,   255,   255, 255, 0,   255, 0,   0,   255
  265. * 255, 255, 0,   0,     0,   0,   255, 0,   0,   0,   0
  266. *
  267. * height = 5, width = 11
  268. * @param args
  269. */
  270. public static int[] imageData = new int[]{
  271. 255, 0,   0,   255,   0,   255, 255, 0,   255, 255, 255,
  272. 255, 0,   0,   255,   0,   255, 255, 0,   0,   255, 0,
  273. 255, 0,   0,   0,     255, 255, 255, 255, 255, 0,   0,
  274. 255, 255, 0,   255,   255, 255, 0,   255, 0,   0,   255,
  275. 255, 255, 0,   0,     0,   0,   255, 0,   0,   0,   0
  276. };
  277. public static void main(String[] args) {
  278. FastConnectedComponentLabelAlg ccl = new FastConnectedComponentLabelAlg();
  279. int[] outData = ccl.doLabel(imageData, 11, 5);
  280. for(int i=0; i<5; i++) {
  281. System.out.println("--------------------");
  282. for(int j = 0; j<11; j++) {
  283. int index = i * 11 + j;
  284. if(j != 0) {
  285. System.out.print(",");
  286. }
  287. System.out.print(outData[index]);
  288. }
  289. System.out.println();
  290. }
  291. }
  292. }

找到最大连通区域以后,对最大连通区域数据进行扫描,找出最小点,即矩形区域左上角坐

标,找出最大点,即矩形区域右下角坐标。知道这四个点坐标以后,在原图上打上红色矩形

框,标记出脸谱位置。寻找四个点坐标的实现代码如下:

[java] view plaincopy
  1. private void getFaceRectangel() {
  2. int width = resultImage.getWidth();
  3. int height = resultImage.getHeight();
  4. int[] inPixels = new int[width*height];
  5. getRGB(resultImage, 0, 0, width, height, inPixels);
  6. int index = 0;
  7. int ta = 0, tr = 0, tg = 0, tb = 0;
  8. for(int row=0; row<height; row++) {
  9. for(int col=0; col<width; col++) {
  10. index = row * width + col;
  11. ta = (inPixels[index] >> 24) & 0xff;
  12. tr = (inPixels[index] >> 16) & 0xff;
  13. tg = (inPixels[index] >> 8) & 0xff;
  14. tb = inPixels[index] & 0xff;
  15. if(tr == tg && tg == tb && tb == 0) { // face skin
  16. if(minY > row) {
  17. minY = row;
  18. }
  19. if(minX > col) {
  20. minX = col;
  21. }
  22. if(maxY < row) {
  23. maxY = row;
  24. }
  25. if(maxX < col) {
  26. maxX = col;
  27. }
  28. }
  29. }
  30. }
  31. }

缺点:

此算法不支持多脸谱检测,不支持裸体中的脸谱检测,但是根据人脸的

生物学特征可以进一步细化分析,支持裸体人脸检测

图像处理------简单脸谱检测算法相关推荐

  1. 图像处理之简单脸谱检测算法

    from: http://blog.csdn.net/jia20003/article/details/7596443 图像处理之简单脸谱检测算法(Simple Face Detection Algo ...

  2. 数字图像处理——隐形眼镜缺陷检测算法

    数字图像处理--隐形眼镜缺陷检测算法 摘 要:本文致力于寻找出一种具有较强鲁棒性的检测隐形眼镜边缘缺陷的方法.本文针对图像中物体几何形状的特殊性,提出了一种基于霍夫变换的缺陷检测算法,并在低噪声图像的 ...

  3. 【图像处理】纹理检测算法

    图像纹理检测算法 LBP检测算法 原文链接:https://blog.csdn.net/tiandijun/article/details/45561981 https://blog.csdn.net ...

  4. python 图像处理 角点检测算法 Harris和Shi-tomasi

    一.使用opencv库调用实现编写Harris和Shi-tomasi算法 最主要函数为: cv2.cornerHarris() cv2.goodFeaturesToTrack() 代码中注释有介绍其用 ...

  5. 详解车道线检测算法之传统图像处理

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 / 导读 / 车道线检测算法分为传统图像处理方法和深度学习方法.本文详细介绍用传统图像处理方法来解决车 ...

  6. 文献阅读笔记【5】:基于图像处理的膨胀圆裂缝检测算法

    论文 | 基于图像处理的膨胀圆裂缝检测算法 作者 | 吴玉龙,岳大森,丁 勇,卢康昕,赵广辉 期刊 | 材料与测试-无损检测-实验研究 时间 | 2020 该文章提出了一种计算裂缝宽度的算法,其过程使 ...

  7. PFLD:简单、快速、超高精度人脸特征点检测算法

    作者 | 周强(CV君) 来源 | 我爱计算机视觉(公众号id:aicvml) 60s测试:你是否适合转型人工智能? https://edu.csdn.net/topic/ai30?utm_sourc ...

  8. 摄像头Optical Center检测算法简单实现

    摄像头Optical Center检测算法简单实现 光线通过透镜时,都会产生偏折,使光线传播方向发生变化:但透镜上有一点,任意方向的光线通过该点时,光线的传播方向不变,即出射方向和入射方向相互平行,这 ...

  9. 基于光电检测图像处理目标检测算法相较传统检测技术的优势

    光电检测图像处理目标检测算法相较传统检测技术有以下几个优势: 速度更快:光电检测图像处理算法可以在较短的时间内处理大量的图像数据,而传统检测技术需要更长的时间来进行目标检测. 精度更高:光电检测图像处 ...

最新文章

  1. Java Swing Button控件点击事件的几种写法
  2. mysql有选择地输出数据_有条件地选择MYSQL列
  3. 显著改善分割预测,ETH开源基于情景图储存网络的视频目标分割|ECCV2020
  4. android studio防止反编译,防反编译利器-Android studio混淆代码压缩apk包体积
  5. Java 学习总结(187)—— 轻量级开源日志框架 tinylog 简介
  6. vue中检测敏感词,锚点
  7. Windows 发布本地提权0day,可以系统权限执行任意代码
  8. 凯尔卡C68全球版汽车电脑诊断仪
  9. 分享一个帮助你有效避免SQL Injection攻击的在线手册 - bbobby-tables.com
  10. 计算机室和电子备课室管理制度,电子备课室
  11. 结构体Sqlist L与Sqlist L的区别
  12. [技术杂谈][转载]cuda下载官方通道
  13. 案例分析十大管理领域理论背诵要点
  14. html div区域划分、居中各种前端技巧笔记
  15. 菜鸟的草缸 篇四:菜鸟的草缸:二氧化碳CO2
  16. android ios mp4格式转换,ios格式转换器|iphone视频格式转换器免费版 7.1 - 系统天堂...
  17. 弱电流检测必备的保护环
  18. 服务网关 Zuul基本使用
  19. nodejs文件服务器
  20. leetcode-377:组合总和 Ⅳ

热门文章

  1. 记一次Vue全页面SSR深坑之旅 - 微弱的内存/CPU泄漏
  2. 老树发新芽—使用 mobx 加速你的 AngularJS 应用
  3. android 扫描SDCard.
  4. OSPF外部实验详解
  5. Tomcat学习--源码导入和运行
  6. 摇一摇根据城市位置推荐酒店
  7. structs - 标签库(html)
  8. Q134:PBRT-V3,次表面散射(Subsurface Scattering)(15.5章节)
  9. FAILED Execution Error, return code 2 from org
  10. python3.7操作kafka_python操作kafka