| 算法 | 公式 | 特點 |
| 分量法 | Gray = R / G / B | 簡單,信息損失大 |
| 最大/最小值法 | Gray = max(R,G,B) / min(R,G,B) | 對比度極端 |
| 平均值法 | Gray = (R + G + B) / 3 | 忽視人眼敏感度差異 |
| 加權平均法(亮度法) | Gray = 0.30R + 0.59G + 0.11B | 符合人眼視覺感知,ITU-R BT.601 標準 |
註:OpenCV/PIL 內部使用 Y = 0.299R + 0.587G + 0.114B(BT.601)或 0.2126R + 0.7152G + 0.0722B(BT.709),係數微異但原理一致。
常見錯誤:直接使用 TYPE_BYTE_GRAY
// 錯誤示範:直接繪制到灰度 BufferedImage
BufferedImage gray = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < w; x++)
for (int y = 0; y < h; y++)
gray.setRGB(x, y, src.getRGB(x, y)); // 直接寫入 ARGB 整數
// 結果:顏色信息錯位,灰度值錯誤,細節嚴重丟失
原因:setRGB 期望 ARGB 打包整數,直接寫入會被誤解析為灰度索引,而非亮度計算。
正確實現:手動提取 RGB 通道計算亮度
public static BufferedImage toGrayscale(BufferedImage src) {
int w = src.getWidth(), h = src.getHeight();
BufferedImage gray = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
// 或 TYPE_INT_ARGB 然後手動設置灰度像素
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
int rgb = src.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// ITU-R BT.601 亮度公式
int grayVal = (int) (0.30 * r + 0.59 * g + 0.11 * b);
// 打包為 ARGB:Alpha=255, R=G=B=grayVal
int argb = (255 << 24) | (grayVal << 16) | (grayVal << 8) | grayVal;
gray.setRGB(x, y, argb);
}
}
return gray;
}
OpenCV 驗證像素級一致性
// Python + OpenCV 參考實現
img = cv2.imread('test.jpg')
gray_cv = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # BGR 順序!
cv2.imwrite('cv_gray.jpg', gray_cv)
# 檢查右下角像素
h, w = img.shape[:2]
print(f"原圖 BGR: {img[h-1, w-1]}") # 如 [44, 67, 89]
print(f"OpenCV 灰度: {gray_cv[h-1, w-1]}") # 71
# Java 計算驗證
int r = 89, g = 67, b = 44; // 注意 OpenCV 讀取為 BGR
int gray = (int)(0.30*r + 0.59*g + 0.11*b); // 71 ✓
驗證結果:手動加權算法與 OpenCV cvtColor 輸出**像素值完全一致**,證明算法正確。
性能優化建議
- 查表法:預計算 256×256×256 → 灰度值查表(內存 16MB),單次查表替代浮點運算。
- 整數定點運算:
gray = (r * 306 + g * 601 + b * 117) >> 10(係數 ×1024 取整),避免浮點轉換開銷。
- 並行流 (Java 8+):
IntStream.range(0, w*h).parallel().forEach(idx -> ...) 利用多核。
- 原生庫:生產環境直接調用 OpenCV (
JavaCV/OpenCV Java bindings) 或 ImageIO 內建轉換(ColorConvertOp)。
ColorConvertOp 一鍵轉換(推薦生產)
BufferedImage src = ImageIO.read(new File("test.jpg"));
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op = new ColorConvertOp(cs, null);
BufferedImage gray = op.filter(src, null);
ImageIO.write(gray, "jpg", new File("gray.jpg"));
JDK 內建 ColorConvertOp 使用標準色彩空間轉換,性能優化、算法標準、代碼極簡。
總結
灰度化核心在於亮度加權計算。直接操作 TYPE_BYTE_GRAY 是常見錯誤,必須手動提取 RGB 通道應用 0.30R + 0.59G + 0.11B 公式。驗證方法:對比 OpenCV/PIL 參考實現的像素值。生產代碼優先使用 JDK 內建 ColorConvertOp 或成熟原生庫,手動實現僅用於教學或特殊定製需求。
- 上一篇文章: Java Swing 實現 QQ 風格好友列表面板(可展開收起分組)完整源碼
- 下一篇文章: Java Web 基於 Cookie 實現自動登錄(免輸入用戶名密碼)示例
Copyright ©
程式碼之家 All Rights Reserved