单颗粒坐标与颜色提取

还是上次那张拥有大量颗粒的图像,不同是这次不对颗粒计数,而是统计单颗粒另外两个重要的属性,也就是坐标和数值。

Pasted-image-20240820221315.png-310ff82db8.png

第一版的代码比较简单,这里还是说明一下:

  • requires() 是因为不同 ImageJ 版本中,内置函数或者 run 的command 可能不一致,我这段代码仅在这个版本中测试过。

  • 然后中间的就是 Find Maxima 的命令行版本,不再赘述。

  • 接着的for 循环中,有一个nResults 的关键词,就是当前 Results 窗口中的数据行数

  • 可以通过 getResult(column_name, index) 的方法从 Results 表格中取值

  • 可以通过 setResult() 方法来填写 Results 表格

可以看到这里边显示的 Value 都是负数,这是因为这是一张 RGB 图像。

计算机中用于表示颜色有多种方法,常见的就是三元组,例如(255,0,0) 就分别代表在 Red,Green 和 Blue 三个颜色通道的分量,那么这个三元组表示的就是红色。也有使用十六进制的,那么就变成了 #ff0000 。但这里的像素值显示的是负数,就让人搞不懂了。查了一下,ImageJ用的是十六进制的,但是要使用位运算才能转换为直观的(R,G,B)三元组。那么修改后的代码如下:

requires("1.53c");
h = 10;
run("Find Maxima...", "prominence="+h+" strict exclude output=List");
for (i = 0; i < nResults; i++) {
x = getResult("X",i);
y = getResult("Y",i);
RGB = getRGB(x, y);
setResult("R", i, RGB[0]);
setResult("G", i, RGB[1]);
setResult("B", i, RGB[2]);
}
function getRGB(x,y){
RGB = newArray(3);
v = getPixel(x,y);
RGB[0] = (v>>16)&0xff; // extract red byte (bits 23-17)
RGB[1] = (v>>8)&0xff; // extract green byte (bits 15-8)
RGB[2] = v&0xff; // extract blue byte (bits 7-0)
return RGB;
}

结果如下:

Pasted-image-20240820222613.png-971579ebef.png

但是这里又带来一个新问题,RGB是计算机友好的颜色表达方式,有三个值。而我们人类日常描述一个颜色,蓝黄红绿橙,就一个值。如果我们想对颜色进行分类,语言中很好分类,但是如果用 RGB 来,就涉及比较复杂的判断。例如黄色是由红色和绿色组成的,判断红色,可能就需要 R 和 G 两通道明显大于 B 通道。而且因为 RGB 颜色空间并非与我们所谓的颜色的概念是线性映射的,所以使用 RGB 来做进一步的基于颜色的分类就比较困难。

好在还有其它的色彩空间,比如HSB色彩空间。其中H代表 Hue,是色度的意思;S代表 Saturation,代表颜色的饱和度;B代表Brightness,代表亮度。通过 HSB,就把像素的亮度和颜色分开了。HSB色彩空间不多科普,而 RGB到HSB的转换,使用 ImageJ 的 Macro 来编写的换算函数,这里我们直接调用就行:

requires("1.53c");
h = 10;
run("Find Maxima...", "prominence="+h+" strict exclude output=List");
for (i = 0; i < nResults; i++) {
x = getResult("X",i);
y = getResult("Y",i);
RGB = getRGB(x, y);
setResult("R", i, RGB[0]);
setResult("G", i, RGB[1]);
setResult("B", i, RGB[2]);
HSB = RGB2HSB(RGB);
setResult("Hue", i, HSB[0]);
setResult("Sat", i, HSB[1]);
setResult("Bri", i, HSB[2]);
}
function getRGB(x,y){
RGB = newArray(3);
v = getPixel(x,y);
RGB[0] = (v>>16)&0xff; // extract red byte (bits 23-17)
RGB[1] = (v>>8)&0xff; // extract green byte (bits 15-8)
RGB[2] = v&0xff; // extract blue byte (bits 7-0)
return RGB;
}
function RGB2HSB(rgb){
r = rgb[0]; // red
g = rgb[1]; // green
b = rgb[2]; // blue
Array.getStatistics(rgb, min, max, mean, stdDev);
if (max==min) h = NaN; //h(0-360)
else if (max==r && g>=b) h = 60*(g-b)/(max-min);
else if (max==r && g<b) h = 60*(g-b)/(max-min)+360;
else if (max==g) h = 60*(b-r)/(max-min)+120;
else if (max==b) h = 60*(r-g)/(max-min)+240;
if (max==0) s = 0; //s(0-1)
else s = 1-min/max;
b = max; //b(0-255)
HSB = newArray(h,s,b);
return HSB;
}

运行结果如下:

Pasted-image-20240820224200.png-bd112512bb.png

直接在 ImageJ 中快速作图查看下 Hue 和 Brightness 的相关性,

Pasted-image-20240820225604.png-b5a1df2e97.png

可以看到大部分颗粒颜色色度在 180-250 之间,亮度在40-110 之间,这说明大部分点都是比较暗的偏蓝色的点。

Pasted-image-20240820225851.png-c3b3712393.png

那么这个确实是符合我们人眼的感受的。