我正在尝试使用OpenCV 3.1 Java操纵单个图像像素。OpenCV将图像读为字节数组。我想将颜色图像转换为灰色图像,而不会更改功能y = r*0.299 g*0.587 b*0.114的图像通道的总数,因此结果仍然是RGB图像(3个颜色通道)。从我的理解中,因为0.299 0.587 0.114 = 1的总和从-128到127,但在施放到字节时,下流或溢出的总和应该没有问题。但是结果很奇怪,因为图像的某些白色区域变成了黑色,有些黑色区域变为白色。我认为底流或溢出发生了。这是我的代码:
List<Mat> channels = new ArrayList<>();
Core.split(intImage, channels);
int totalInt = (int)(channels.get(0).total());
byte[] blue = new byte[totalInt];
byte[] green = new byte[totalInt];
byte[] red = new byte[totalInt];
channels.get(0).get(0, 0, blue);
channels.get(1).get(0, 0, green);
channels.get(2).get(0, 0, red);
for (int i = 0; i < totalInt; i++) {
byte s = (byte)(blue[i]*0.114 + green[i]*0.587 + red[i]*0.299);
blue[i] = red[i] = green[i] = s;
}
channels.get(0).put(0, 0, blue);
channels.get(1).put(0, 0, green);
channels.get(2).put(0, 0, red);
Mat gray = new Mat();
Core.merge(channels, gray);
我尝试将图像转换为CvType.CV_16S
,该图像代表未签名的简短,并且到目前为止毫无问题地工作了。转换为CV_8UC3仍在字节中。我担心堆问题,因为当我尝试使用INT(即CV_32S)时,一些大图像发生了堆错误。所以这是我的问题:
- 如果可以的话,我如何防止或处理那些溢出/底流。我仍然考虑使用字节,因为它会减少堆/内存使用量。
- 如果第一个是唯一的选项,那么我如何可以将CV_16直接转换为BufferedImage,而无需转换回原始垫子类型,因为我正在使用Swing显示图像。
我找到了从MAT转换为BufferedImage
的方法,如下所示:
public BufferedImage toBufferedImage(Mat matrix) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (matrix.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
BufferedImage image = new BufferedImage(matrix.cols(),
matrix.rows(), type);
final byte[] targetPixels =
((DataBufferByte)image.getRaster().getDataBuffer()).getData();
matrix.get(0, 0, targetPixels);
return image;
}
请解释一下,字节铸造发生了什么。
blue[i]*0.114 + green[i]*0.587 + red[i]*0.299
(您应该绝对更改为
red[i]*0.299 + green[i]*0.587 + blue[i]*0.114
保持RGB订单完整!)
正在使用字节的值进行。正如您已经指出的那样,这些值是负面。这会导致错误的结果计算。
尽管对于(签名的) byte
,但命名值-16和正值240以相同的位模式表示,但它们仍然是不同的值,并且必须在计算过程中考虑这一点。
考虑以下示例:
public class PixelBytes
{
public static void main(String[] args)
{
byte r = (byte)(100);
byte g = (byte)(240);
byte b = (byte)0;
compute(r, g, b);
}
private static byte compute(byte r, byte g, byte b)
{
byte sr = (byte) (r * 0.299);
byte sg = (byte) (g * 0.587);
byte sb = (byte) (b * 0.114);
byte s = (byte) (sr + sg + sb);
byte tr = (byte) (toUnsignedInt(r) * 0.299);
byte tg = (byte) (toUnsignedInt(g) * 0.587);
byte tb = (byte) (toUnsignedInt(b) * 0.114);
byte t = (byte) (tr + tg + tb);
System.out.println("For " + r + " " + g + " " + b);
System.out.println("add " + sr + " " + sg + " " + sb + " to get " + s);
System.out.println("or " + tr + " " + tg + " " + tb + " tp get " + t);
return s;
}
// Same as Byte#toUnsignedInt in Java 8
private static int toUnsignedInt(byte b)
{
return ((int) b) & 0xff;
}
}
输出将为
For 100 -16 0
add 29 -9 0 to get 20
or 29 -116 0 tp get -87
清楚地表明结果有所不同,具体取决于是否使用了负值,或者是否首先将值转换为"真实"无符号值。
因此,可以通过这样的方法来完成灰色像素值的计算:
private static byte computeLuminance(byte r, byte g, byte b)
{
int ir = r & 0xFF;
int ig = g & 0xFF;
int ib = b & 0xFF;
return (byte)(ir * 0.299 + ig * 0.587 + ib * 0.114);
}