当操作OpenCV 3.1 Java中的单个像素时,如何预防或处理字节溢出和字节下流



我正在尝试使用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);
}

最新更新