背景介绍

之前应一个中科院生态环境研究院的朋友邀请,为其所在团队开发一款调查问卷形式的小程序,其中有一个主要功能是需要用户上传所处位置东南西北四个方向的照片,然后在服务端对这四张图片进行解析,提取出图片中人眼所见绿色所占整张图片的比例。

打个比方,如下这张图片

人眼所见的话,绿色占比大概在 30% ~ 40% ,我服务端的应用程序需要把这个绿视率计算出来,并保存在数据库里。

碍于没有图像处理的相关经验,没有立即答应朋友,而是回复他给我两天时间,能不能做,两天后回复他。

之后两天,自己便查阅了相关资料,了解了图像色彩方面的相关知识,诸如 RGB、HSV、像素点矩阵之类的,之后动手写了个简单的 demo,八九不离十的实现了这个色彩提取的功能。

这篇文章我把它归类在自己的开发杂记这个分类里,所以可能啰嗦的有点多了,请各位看官见谅,废话不多说,下面开始进入正题,说明我的解决思路和贴上源代码。

从 RGB 到 HSV

在进行绿色像素提取之前,需要明确一个概念,每一张图片都是由若干个像素点组成的。像素点的个数就是图片的长宽之积,如下

那么这张图的像素点个数就是 1440 x 1080 = 1555200 个。

实现绿色像素的提取就是在这些像素点中找到哪些可以被判定为绿色的点,然后将其与总像素点个数相除,得到的比例即可大致判定为该图片的绿视率。

一 、RGB

RGB 色彩模式是工业界的一种颜色标准,是通过对红( R )、绿( G )、蓝( B )三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB 即是代表红、绿、蓝三个通道的颜色,是目前运用最广的颜色系统之一。

上面这句话专业吧,没错,我在百度百科里粘过来的。

大白话就是所有色彩都是由红( R )、绿( G )、蓝( B )这三原色组成,我们只需要通过设置一些阈值(三原色之间的关系),将那些在阈值范围内的点判定为绿像素点,之后与总像素点作除法即可得到我们想要的结果。

之后,我便花了一天时间,去查找 R、G、B 之间什么样的关系才能判定为绿色,自己也使用画板调试了很久,最终得到了第一版的实现代码。但是,这种实现方式的结果在某些情况下并不令我满意,查找了很多资料才发现,RGB 虽然表示直接,但是 R、G、B 数值和色彩的三属性没有直接的联系,不能揭示色彩之间的关系。所以在进行配色设计时,RGB 模型就不是那么合适了(可能对于计算机来说,某些值是可以归为绿色,但对于人眼来说,某些点怎么看都不像是绿色。。。),也正因如此,我最终没有采用这种解决方案,所以这里也不提供代码了,在又查阅了其他资料后,我将目光锁定在了 HSV 空间上。

二 、HSV

HSV是指 Hue(色相)、Saturation(饱和度)和 Value(值)。

RGB 和 CMY 颜色模型都是面向硬件的,而 HSV 颜色模型是面向用户的。 即HSV对用户来说是一种直观的颜色模型。

很幸运,Java 提供了将 RGB 转换为 HSV 的 API,我们只需要确定好 H、S、V 三个域之间的阈值范围即可,这个范围的确定,这里就不多说了,都是自己找资料加上慢慢调试出来的,如果你想提取绿色的话,可以直接参考我的阈值范围,别的颜色的话,那么你就自己慢慢调试吧。

下面是代码实现,注释我个人认为做的够详细了,别的就不多说了,不懂的地方看注释,或者给我留言:

package com.util.xgb;import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;import javax.imageio.ImageIO;public class CalculateGreenProportion {// 计算绿色像素所占比例,传入参数为图片的文件路径public static String calculateGreen(String image) throws Exception {File file = new File(image);BufferedImage bi = null;try {bi = ImageIO.read(file);} catch (Exception e) {e.printStackTrace();}//长宽int width = bi.getWidth();int height = bi.getHeight();//横纵坐标起始点int minx = bi.getMinX();int miny = bi.getMinY();//绿色像素点个数long greenNumber = 0;int[] rgb = new int[3];// 定义RGB空间float[] hsv = new float[3];// 定义HSV空间// 开始遍历所有像素点for (int i = minx; i < width; i++) {for (int j = miny; j < height; j++) {// 当前像素点int pixel = bi.getRGB(i, j); // 获取RGB各值rgb[0] = (pixel & 0xff0000) >> 16;//Rrgb[1] = (pixel & 0xff00) >> 8;//Grgb[2] = (pixel & 0xff);//B// rgb转hsvColor.RGBtoHSB(rgb[0], rgb[1], rgb[2], hsv);//使用hsv判断该像素点是否可以判定为绿色像素点if (hsv[2] >= 0.075 && hsv[1] >= 0.15 && hsv[0] > 0.1389 &&hsv[0] <= 0.4444) {greenNumber++;}}}// 总像素点个数long totalPixelNumber = width * height;// 获取浮点数表示的占比double greenPixelProportion = (double) greenNumber / totalPixelNumber;// 返回百分制字符串return translateDoubleIntoPercent(greenPixelProportion);}/*** 将浮点数转换为百分制* @param d* @return*/public static String translateDoubleIntoPercent(double d) {BigDecimal bDecimal = new BigDecimal(d);bDecimal = bDecimal.setScale(4, BigDecimal.ROUND_HALF_UP);DecimalFormat dFormat = new DecimalFormat("0.00%");String result = dFormat.format(bDecimal.doubleValue());return result;}
}

写个测试用例测试一下:

public static void main(String[] args) throws IOException {String probability = null;try {probability = calculateGreen("C:\\Users\\_Miracle\\Desktop\\2019050111265770466.jpg");} catch (Exception e) {e.printStackTrace();}System.out.println("hsv提取绿视率:" + probability);
}

测试的图片,你就将你图片的绝对路径粘到probability = calculateGreen("C:\\Users\\_Miracle\\Desktop\\2019050111265770466.jpg");
这里就可以。


控制台打印结果如下

hsv提取绿视率:38.37%

还算比较准确吧。

使用多线程优化性能

本地计算一些尺寸不算特别大的图片还好,但是上线之后才发现现在这手机像素都非常高啊。。。一张照片大小动不动就5、6MB,如果用户选择上传原图的话,由于网络、IO的制约,再加上计算绿视率需要遍历所有像素点(比如我这渣渣手机,拍出来的照片尺寸都是4608 x 3456左右的,那么就需要遍历 15925248 个像素,近1600万个啊。。。),服务端的计算结果需要延时一会儿才能反馈回客户端,用户体验非常不好。

针对此,可以充分利用服务器多核优势。。。虽然我的服务器才双核。。。但那也是可以利用并发提高性能的!

我的想法是,由几个线程平分总共的像素点数目,分别计算分配给它的像素点中有多少个可以判定为绿色像素点的,判断完了将其返回即可,最终由主线程汇总各个子线程的判定结果,之后的步骤就跟第一个版本的代码逻辑一样了。

因为需要线程具有返回值,所以可以将任务封装到实现 Callable 接口的类中,之后使用 FutureTask 获取返回结果。关于如何实现线程传参和处理线程返回值这里就不多做介绍了,下面贴代码。

首先是子任务的实现类:

package com.util.xgb;import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.concurrent.Callable;public class CalculateTask implements Callable<Long>{// 横纵坐标起始点public int startX;public int startY;// 横纵坐标终点public int endX;public int endY;// 持有BufferedImage引用public BufferedImage bi;// 使用构造函数传参public CalculateTask(int sx,int sy,int ex,int ey,BufferedImage bImage) {startX = sx;startY = sy;endX = ex;endY = ey;bi = bImage;}@Overridepublic Long call() throws Exception {// 定义HSV空间float[] hsv = new float[3];long res = 0L;// 开始遍历分配给该线程的像素点for (int i = startX; i < endX; i++) {for (int j = startY; j < endY; j++) {// 当前像素点int pixel = bi.getRGB(i, j);// 获取RGB各值int R = (pixel & 0xff0000) >> 16;// Rint G = (pixel & 0xff00) >> 8;// Gint B = (pixel & 0xff);// B// rgb转hsvColor.RGBtoHSB(R, G, B, hsv);//使用hsv判断该像素点是否可以判定为绿色像素点if (hsv[2] >= 0.075 && hsv[1] >= 0.15 && hsv[0] > 0.1389 && hsv[0] <= 0.4444) {res++;}}}System.out.println("当前线程:" + Thread.currentThread().getName() + ",绿色像素点个数:" + res);return res;}
}

之后,更改之前的主类代码如下,这里另外开启了两个线程去平分像素点:

package com.util.xgb;import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.concurrent.FutureTask;import javax.imageio.ImageIO;public class Calculate{public static String calculateGreen(String image) throws Exception {File file = new File(image);BufferedImage bi = null;try {bi = ImageIO.read(file);} catch (Exception e) {e.printStackTrace();}int width = bi.getWidth();int height = bi.getHeight();int minx = bi.getMinX();int miny = bi.getMinY();// 平分任务,使用 FutureTask 包装,获取返回值。FutureTask<Long> fTask1 = new FutureTask<>(new CalculateTask(minx, miny, width / 2, height, bi));FutureTask<Long> fTask2 = new FutureTask<>(new CalculateTask(width / 2 + 1, miny, width, height, bi));new Thread(fTask1).start();new Thread(fTask2).start();long res1 = fTask1.get();long res2 = fTask2.get();//由主线程汇总long totalGreenPixel = res1 + res2;long totalPixelNumber = width * height;double greenPixelProportion = (double) totalGreenPixel / totalPixelNumber;return translateDoubleIntoPercent(greenPixelProportion);}/*** 将浮点数转换为百分制* @param d* @return*/public static String translateDoubleIntoPercent(double d) {BigDecimal bDecimal = new BigDecimal(d);bDecimal = bDecimal.setScale(4, BigDecimal.ROUND_HALF_UP);DecimalFormat dFormat = new DecimalFormat("0.00%");String result = dFormat.format(bDecimal.doubleValue());return result;}
}

我们在测试函数里加上计时来观察性能提上多少(在之前的单线程代码中也加入,对二者进行对比):

public static void main(String[] args) throws IOException {long start = System.currentTimeMillis();String probability = null;try {probability = calculateGreen("C:\\Users\\_Miracle\\Desktop\\cbd99c9c34dcbf7fd9328686d243bbe.jpg");} catch (Exception e) {e.printStackTrace();}long time = System.currentTimeMillis() - start;System.out.println("hsv提取绿视率:" + probability + ",耗时:" + time + "ms");}

这次测试我们就使用一张比较大的图片,如下所示,它有 4608 x 3456 = 15925248 个像素点,我们来观察一下结果。

首先是原来单线程的结果。

接下来看看使用多线程优化后的结果。

性能提升还是相当明显的,速度快了40%左右。

提取图片色彩比例(Java 代码实现)相关推荐

  1. 压缩图片大小的java代码_java按比例压缩图片的源代码,用java如何把图片处理到指定大小...

    [要分析某个网页中的代码构成,需要某个结点下的内容.用此原始方法可以得到整个网页的源码.其实更简单的方法是使用 WebClient 或 HtmlUtil 等开源方式 .public class Ht ...

  2. java将图片转成缩略图,将图片生成缩略图Java代码实现

    在工作中经常会遇到图片处理相关的需求,比如说一些图片网站只展示相关的缩略图,而真实的图片可能很大而不是直接展示.所以就需要在上传相关的图片后直接对图片进行处理生成相关的缩略图.实现代码如下: impo ...

  3. java 上传图片 生成缩略图_将图片生成缩略图Java代码实现

    在工作中经常会遇到图片处理相关的需求,比如说一些图片网站只展示相关的缩略图,而真实的图片可能很大而不是直接展示.所以就需要在上传相关的图片后直接对图片进行处理生成相关的缩略图.实现代码如下: impo ...

  4. java 将图片转成二进制文件bin_java 问题:怎样把一个bin二进制图片文件用java代码打开?求解!...

    展开全部 Java中可以用java.awt.Toolkit类打开gif,jpg,png三种类型的二进制图片文件,如果是62616964757a686964616fe59b9ee7ad943133333 ...

  5. html图片缩放6,四款css 图片按比例缩放实例(兼容ie6,7,firefox)

    使用max-width,max-height:或者min-width,min-height的css属性即可.如: 代码如下 img{max-width:100px;max-height:100px;} ...

  6. html 设置图片显示比例,css怎么实现图片在页面上以相同等比例显示缩放?(示例)...

    在我们浏览页面时或者实际工作中,偶尔会遇到图片缩放的问题.那么本篇文章就给大家介绍关于css 图片等比例缩放即css图片等比例显示的问题.希望对有需要的朋友有所帮助. css图片等比例显示具体代码示例 ...

  7. java获取图片主色_Java获取彩色图像中的主色彩的实例代码

    本文讲述了java获取彩色图像中的主色彩的实例代码.分享给大家供大家参考,具体如下: 一:基本思路 对于一张rgb色彩空间的彩色图像,很多时间我们想通过程序获得该图像有几种主要的色彩,但是对一般图像来 ...

  8. Java使用Tesseract-OCR文字识别(Java调用tess4j提取图片中文、英文、数字信息)

    由于需要在应用中将原本的身份认证手动提交身份信息改为用户上传身份证照自动提取信息,提升用户体验,第一时间想到阿里云等平台的收费服务及开源技术Tesseract-OCR(Tesseract-OCR提供了 ...

  9. java提供图片链接,提取网页的图片链接的Java程序

    提取网页的图片链接的Java程序 输入网页文件名,和资源列表文件名 输出资源列表文件供迅雷下载. 适用于批量下载图片. 由两个文件组成. 没有提供网页下载功能,因为我没有时间写,相关的代码以后再贴. ...

最新文章

  1. 【爬坑】在 IDEA 中运行 Hadoop 程序 报 winutils.exe 不存在错误解决方案
  2. 网络工程师60道典型选择题
  3. Spring中的Events
  4. SAP OData编程指南
  5. pcie1 4 速度_太阳系行星们谁转得最快?八大行星自转速度排行榜,地球排第五...
  6. java中选择排序和冒泡排序_Java选择排序就是比冒泡排序牛「具体详情,请看此文」...
  7. 智能会议系统(16)---Linphone配置大全
  8. MPLS 次末跳弹出配置_weblogic的安装与配置
  9. sizeof和strlen区别
  10. socks5 转换为 http 代理(使用privoxy)
  11. python爬取音乐网站排行榜_使用Python抓取Web端QQ音乐排行榜 批量下载QQ音乐到本地...
  12. Art of Problem Solving: Proof without Words
  13. python宣传视频 抖音_Python生成抖音字符视频,技术流!
  14. 从用户个体的角度,谈微信群吱口令红包
  15. 吴军:顶级工程师能让中国走向浪潮之巅
  16. 美国专利诉讼地之争议:从苹果考虑关闭美国德州东区门市说起
  17. LeetCode T48 Rotate Image
  18. 《和平精英》×开心麻花电影《独行月球》的讨巧互利联动
  19. python的简单爬取代码之小白教程(微博热门标题)
  20. linux服务器有电信和网通,linux双线路双ip,设置电信和网通走不同的路由。

热门文章

  1. java一键_Java环境安装-Java环境一键安装下载免安装版-西西软件下载
  2. Ubuntu(Linux)增加新用户并赋予权限、删除用户
  3. android设置padding单位,android – 什么是paddingStart和paddingEnd?
  4. Android文本框示例
  5. 这样写代码真快——idea自动补全总结
  6. Access开发之只显示窗体
  7. 如何在CAD编辑软件中设置图纸的旋转角度
  8. 今天过生日,煲靓汤庆祝,好叻!--介绍“番茄薯子汤”煲制方法给大家:)
  9. Element Plus图标显示使用教程
  10. word2013 表格的行高度改不小怎么办?