JPEG 压缩——第三步:YCbCr 色彩空间
在 YCbCr 色彩空间内,将 Y 乘以0.7以使对比度变暗。
YCbCr 色彩空间是用于将图像由表示亮度的 Y、表示蓝色色度Cb以及表示红色色度Cr表示的方法。
这用于 JPEG 转换。
使用下式从 RGB 转换到 YCbCr:
Y = 0.299 \ R + 0.5870 \ G + 0.114 \ B\\
Cb = -0.1687\ R – 0.3313 \ G + 0.5 \ B + 128\\
Cr = 0.5 \ R – 0.4187 \ G – 0.0813 \ B + 128
使用下式从 YCbCr 转到 RGB:
R = Y + (Cr – 128) \ 1.402\\
G = Y – (Cb – 128) \ 0.3441 – (Cr – 128) \ 0.7139\\
B = Y + (Cb – 128) \ 1.7718
python实现:
import cv2
import numpy as np
import matplotlib.pyplot as plt
channel = 3
# BGR -> Y Cb Cr
def BGR2YCbCr(img):
H, W, _ = img.shape
ycbcr = np.zeros([H, W, 3], dtype=np.float32)
ycbcr[..., 0] = 0.2990 * img[..., 2] + 0.5870 * img[..., 1] + 0.1140 * img[..., 0]
ycbcr[..., 1] = -0.1687 * img[..., 2] - 0.3313 * img[..., 1] + 0.5 * img[..., 0] + 128.
ycbcr[..., 2] = 0.5 * img[..., 2] - 0.4187 * img[..., 1] - 0.0813 * img[..., 0] + 128.
return ycbcr
# Y Cb Cr -> BGR
def YCbCr2BGR(ycbcr):
H, W, _ = ycbcr.shape
out = np.zeros([H, W, channel], dtype=np.float32)
out[..., 2] = ycbcr[..., 0] + (ycbcr[..., 2] - 128.) * 1.4020
out[..., 1] = ycbcr[..., 0] - (ycbcr[..., 1] - 128.) * 0.3441 - (ycbcr[..., 2] - 128.) * 0.7139
out[..., 0] = ycbcr[..., 0] + (ycbcr[..., 1] - 128.) * 1.7718
out = np.clip(out, 0, 255)
out = out.astype(np.uint8)
return out
# Read image
img = cv2.imread("imori.jpg").astype(np.float32)
# bgr -> Y Cb Cr
ycbcr = BGR2YCbCr(img)
# process
ycbcr[..., 0] *= 0.7
# YCbCr > RGB
out = YCbCr2BGR(ycbcr)
# Save result
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.imwrite("out.jpg", out)
c++实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
#include <complex>
// BGR -> Y Cb Cr
cv::Mat BGR2YCbCr(cv::Mat img, cv::Mat out){
int width = img.rows;
int height = img.cols;
//cv::Mat out = cv::Mat::zeros(height, width, CV_32F);
for (int j = 0; j < height; j ++){
for (int i = 0; i < width; i ++){
// Y
out.at<cv::Vec3b>(j, i)[0] = (int)((float)img.at<cv::Vec3b>(j,i)[0] * 0.114 + \
(float)img.at<cv::Vec3b>(j,i)[1] * 0.5870 + \
(float)img.at<cv::Vec3b>(j,i)[2] * 0.299);
// Cb
out.at<cv::Vec3b>(j, i)[1] = (int)((float)img.at<cv::Vec3b>(j,i)[0] * 0.5 + \
(float)img.at<cv::Vec3b>(j,i)[1] * (-0.3323) + \
(float)img.at<cv::Vec3b>(j,i)[2] * (-0.1687) + 128);
// Cr
out.at<cv::Vec3b>(j, i)[2] = (int)((float)img.at<cv::Vec3b>(j,i)[0] * (-0.0813) + \
(float)img.at<cv::Vec3b>(j,i)[1] * (-0.4187) + \
(float)img.at<cv::Vec3b>(j,i)[2] * 0.5 + 128);
}
}
return out;
}
// Y Cb Cr -> BGR
cv::Mat YCbCr2BGR(cv::Mat ycbcr, cv::Mat out){
int width = out.rows;
int height = out.cols;
for (int j = 0; j < height; j ++){
for (int i = 0; i < width; i ++){
// R
out.at<cv::Vec3b>(j, i)[2] = (uchar)(ycbcr.at<cv::Vec3b>(j, i)[0] + (ycbcr.at<cv::Vec3b>(j, i)[2] - 128) * 1.4102);
// G
out.at<cv::Vec3b>(j, i)[1] = (uchar)(ycbcr.at<cv::Vec3b>(j, i)[0] - (ycbcr.at<cv::Vec3b>(j, i)[1] - 128) * 0.3441 - (ycbcr.at<cv::Vec3b>(j, i)[2] - 128) * 0.7139);
// B
out.at<cv::Vec3b>(j, i)[0] = (uchar)(ycbcr.at<cv::Vec3b>(j, i)[0] + (ycbcr.at<cv::Vec3b>(j, i)[1] - 128) * 1.7718);
}
}
return out;
}
cv::Mat process(cv::Mat ycbcr){
int width = ycbcr.rows;
int height = ycbcr.cols;
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
ycbcr.at<cv::Vec3b>(y, x)[0] *= 0.7;
}
}
return ycbcr;
}
// Main
int main(int argc, const char* argv[]){
// read original image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
int width = img.rows;
int height = img.cols;
// output image
cv::Mat ycbcr = cv::Mat::zeros(height, width, CV_32FC3);
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);
// BGR -> Y Cb Cr
ycbcr = BGR2YCbCr(img, ycbcr);
// Process
ycbcr = process(ycbcr);
// Y Cb Cr -> BGR
out = YCbCr2BGR(ycbcr, out);
cv::imwrite("out.jpg", out);
//cv::imshow("answer", out);
//cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
输入:
输出: