[Java]图像颜色匹配算法的实施
在问答频道中看到这个问题:
要分析的图片如下所示:
于是设计了算法:
0. 建立一个颜色分区数组为a,内包含颜色区间值v
1. 建立一个颜色统计MultiValuedMap称为m:key -> v; value -> [x, y] x, y为图片中像素坐标
2. 读取图片f
3. 读取f中每一个像素的hsl
4. 计算这个像素的hsl值与a中所有已有颜色区间v的偏差d
* 如果d在允许的范围s内,则将此值计入到m
* 如果d大于s,在a中建立新的v,并在m中新添此坐标
a. 这个算法中,s将决定颜色的相似度,s越小,颜色划分的越细
b. 最后的结果将从m中出,每一个key所包含的value数量,将成为颜色的百分比计算依据
在上面的算法中,需要用到一些基础工具,比如MultiValuedMap和HSLColor。
其中MultiValuedMap可以支持我们使用一个KEY保存多个值。因为我们要匹配三种颜色,即:红,绿,蓝。所有匹配到的图片中的颜色,要把它们的坐标计入到三种颜色中的一个。因此,颜色就是KEY,坐标就是VALUE。
下面是MultiValuedMap的代码:
import java.util.*;public class MultiValueMap<K, V> { private Map<K, Collection<V>> map; private Class<? extends Collection<V>> clazz; public MultiValueMap(Collection<V> coll) { map = new HashMap<K, Collection<V>>(); this.clazz = (Class<? extends Collection<V>>) coll.getClass(); } public void addValue(K key, V value) { Collection<V> collection = map.get(key); if (collection == null) { collection = createCollection(); if (collection == null) return; map.put(key, collection); } collection.add(value); } public Set<K> keys() { return map.keySet(); } public Collection<V> getValues(K key) { Collection<V> collection = map.get(key); if (collection == null) return Collections.emptySet(); return collection; } private Collection<V> createCollection() { Collection<V> collection = null; try { collection = clazz.newInstance(); } catch (InstantiationException ex) { // handling here } catch (IllegalAccessException ex) { // handling here } return collection; }}
import javax.imageio.ImageIO;import javax.swing.*;import java.awt.*;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class Picker { // Hue偏差允许0.5% private static final double Hd = 0.5; // Saturation偏差允许0.5% private static final double Sd = 0.5; // Luminance编差允许1% private static final double Ld = 1.0; // 图中的绿色样本 private static final Color GREEN = new Color(46, 189, 102); // 将绿色转化到hsl空间 private static final HSLColor GREEN_HSL = new HSLColor(GREEN); // 图中的蓝色样本 private static final Color BLUE = new Color(87, 91, 212); // 将蓝色转化到hsl空间 private static final HSLColor BLUE_HSL = new HSLColor(BLUE); // 图中的红色样本 private static final Color RED = new Color(238, 48, 50); // 将红色转化到hsl空间 private static final HSLColor RED_HSL = new HSLColor(RED); private static final HSLColor[] COLOR_SAMPLES = {GREEN_HSL, BLUE_HSL, RED_HSL}; // 允许的颜色偏差范围为1.5% private static final double SCOPE = 0.15; public static void main(String[] args) throws IOException { // 读取图片文件 File file = new File(Picker.class.getClassLoader().getResource("").getPath() + "sample.jpg"); BufferedImage bufImg = ImageIO.read(file); int height = bufImg.getHeight(); int width = bufImg.getWidth(); MultiValueMap<Color, Point> mvm = new MultiValueMap<Color, Point>(new ArrayList<Point>()); // 总像素数 int totalPixels = width * height; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { // 获取当前像素的颜色 Color rgbColor = new Color(bufImg.getRGB(i, j)); // 去掉图中所有的白底 if (rgbColor.equals(Color.WHITE)) { totalPixels -= 1; continue; } // 转化到HSL空间 HSLColor color = new HSLColor(rgbColor); // 计算颜色偏差 for (HSLColor sampleColor : COLOR_SAMPLES) { double hDeviation = Math.abs(color.getHue() - sampleColor.getHue()); double sDeviation = Math.abs(color.getSaturation() - sampleColor.getSaturation()); double lDeviation = Math.abs(color.getLuminance() - sampleColor.getLuminance()); double result = Math.sqrt(Hd * hDeviation * hDeviation + Sd * sDeviation * sDeviation + Ld * lDeviation * lDeviation) / 100; if (result < SCOPE) { // 颜色与红,绿,蓝中的一种匹配上了,加入到MultiValueMap当中 mvm.addValue(sampleColor.getRGB(), new Point(i, j)); // 颜色匹配上了,没有必要再计算别的颜色 break; } } } } System.out.println("Green Color: " + ((double) mvm.getValues(GREEN).size() / (double) totalPixels * 100) + "%"); System.out.println("Red Color: " + ((double) mvm.getValues(RED).size() / (double) totalPixels * 100) + "%"); System.out.println("Blue Color: " + ((double) mvm.getValues(BLUE).size() / (double) totalPixels * 100) + "%"); }}
Green Color: 8.915814359870483%Red Color: 3.0156241969471145%Blue Color: 13.816364290486716%
import javax.swing.*;import java.awt.*;public class Points extends JPanel { private MultiValueMap<Color, Point> pixels; public Points(MultiValueMap<Color, Point> pixels) { this.pixels = pixels; } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; for (Color color : pixels.keys()) { g2d.setColor(color); for (Point p : pixels.getValues(color)) { g2d.drawLine(p.x, p.y, p.x, p.y); } } }}
JFrame frame = new JFrame("Result");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.add(new Points(mvm));frame.setSize(width, height);frame.setLocationRelativeTo(null);frame.setVisible(true);
git clone https://github.com/liweinan/image-detect.git
mvn install
mvn exec:java -Dexec.mainClass="net.bluedash.Picker"