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