Image color extraction ratio (Java code implementation)

Background

Prior should be a friend of the Chinese Academy of Sciences Institute of the ecological environment of the invitation, where their small team development program in the form of a questionnaire, which has a main function is to require the user to upload pictures of the location of the four corners, and then on the server these four images is parsed to extract the human eye can see pictures of green proportion occupied the whole picture.

Figuratively, as this picture
Here Insert Picture Description
the human eye can see, then, green accounting for about 30% to 40%, my server application needs to put this green depending on the rate calculated and stored in the database.

No experience because of image processing, friends did not immediately agree, but he gave me two days to reply, can not do, his reply two days later.

After two days he will be access to relevant information, understanding of the relevant aspects of the knowledge of color images, such as RGB, HSV, pixel dot matrix and the like, and then I wrote a simple hands-on demo, pretty close realization of this color extraction function.

This article I classify it in their own development miscellany in this category, it may be a bit more long-winded, please forgive me Tell me, ado, let's start into the topic, explain my ideas and solve paste source code.

From RGB to HSV

Before the green pixel extraction is performed, we need a clear concept, each picture is made up of several pixel dots. The number is the product of length and width of the image pixel, as
Here Insert Picture Description
then the number of pixels in this picture point is 1440 x 1080 = 1555200 th.

Extracting the green pixel is to achieve what may be determined to find the green dot, and then divided by the number of total pixels, the ratio obtained for the image can be substantially determined depending on the rate at which green pixel points.

A, RGB

RGB color model is an industry standard color, by superimposing between the red (R), green (G), and blue three color channels change (B) and their mutual variety of colors to obtain of, RGB that is representative of red, green, and blue color channels, is one of the most widely used color system.

Above this sentence professional, yes, I stick around in Baidu Baike inside.

大白话就是所有色彩都是由红( 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;//R
				rgb[1] = (pixel & 0xff00) >> 8;//G
				rgb[2] = (pixel & 0xff);//B
				
				// rgb转hsv
				Color.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");
这里就可以。

Here Insert Picture Description
控制台打印结果如下

hsv提取绿视率:38.37%

还算比较准确吧。

使用多线程优化性能

Local calculate the size of some not particularly big picture better, but now only to find that the phone is very high ah pixels on the line after. . . A picture size at every turn 5,6MB, if the user chooses to upload artwork, then, due to the constraints network, IO, plus green computing, as the rate need to traverse all pixels (for example, I slag slag this phone, shoot out of the picture size is about 4608 x 3456, you will need to traverse 15,925,248 pixels, nearly 16 million ah ...), the results delayed while the server needs to feedback back to the client, the user experience is very bad.

For this, we can make full use of the advantages of multi-core servers. . . Although I only dual-core server. . . But that also can be used concurrently to improve performance!

My idea is bisected by several threads in total number of pixels, and calculate allocated to it in the number of pixels can be determined to be a green pixel, it will return to judge finished, the final summary of each of the main thread child threads determination result, just after the first step of a version of the same code logic.

Because of the need thread has a return value, it is possible to achieve the task package Callable interface class FutureTask acquired after use return results. On how to achieve mass participation and thread processing thread return value here do not introduced, paste the following code.

The first is the implementation class subtasks:

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;
	}
	
	@Override
	public 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;// R
				int G = (pixel & 0xff00) >> 8;// G
				int B = (pixel & 0xff);// B
				
				// rgb转hsv
				Color.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;
	}
}

Thereafter, before the change of the main class code as follows, where the two additional threads to open bisecting pixels:

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;
	}
}

Timing performance put on to observe how much (also added prior to the single-threaded code, for both comparative) we add the test function in:

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");
		

	}
	

The test we use a larger picture, as shown below, it has a 4608 x 3456 = 15925248 pixel point, let's look at the results.
Here Insert Picture Description
The first is the result of the original single-threaded.
Here Insert Picture Description
Then take a look at the result of the use of multi-threading optimization.
Here Insert Picture Description

Performance is still quite obvious, about 40% faster.

Guess you like

Origin blog.csdn.net/u013568373/article/details/94431069