像素操作
# 像素操作
# 基本概念
- ARGB88/RGB565: 真正在Android手机上显示的像素点的内存格式. 参见:像素格式RGB (opens new window)
- yuv 文件压缩前/解压后的采样格式.最常用yuv420.yuv是个中间状态的格式,主要为压缩算法而生
- jpg/png/webp/gif: 图像文件压缩后的存储格式,其中jpg,webp均有元数据exif. 各压缩格式也对应了相应格式的编解码算法
- H.264/H.265: 当下最常用视频编解码算法
- Mp4/avi/mkv: 视频压缩后的封装格式,包括帧数据和元数据
封装格式 vs 编码格式 vs 元数据
总结:
Android和iOS项目中,从摄像头来的数据都是YUV格式的;
解码一个视频时,每一帧中buffer里的data也都是YUV格式的
而:
UI界面显示画面需要使用RGB格式的data;
OpenCV等库需要RGB格式的输入;
神经网络需要RGB这3个channel的输入.
图像与视频编码基础知识 (opens new window)
# YUV
Y 表示明亮度(Luminance、Luma),而 U 和 V 表示色度(Chrominance、Chroma)。
而色度又定义了颜色的两个方面:色调和饱和度
一个bitmap对象变成图像文件的过程:
RGB->yuv->压缩成jpg/png
视频通信系统之所以要采用YUV,而不是RGB,主要是因为RGB信号不利于压缩
与下面HSL的不同: yuv是包含了色彩表示和采样模式. 而下方色彩空间中的HSL是单个像素点的色彩表示方法
一文读懂 YUV 的采样与格式 (opens new window)
最常用的采样模式是yuv420: 周围4个像素共用色度
yuv不直接用来存储,而是用作压缩算法的输入,最终压缩成jpg/png等格式存储在文件系统中.
比如图片压缩过程中,先将RGB转换为YUV420,然后使用jpg/png压缩算法压缩成jpg/png格式
# RGB与YUV转换公式
为什么有些手机glide图片加载后白色背景会变绿?
安卓的一个skia的代码,为了优化执行速度对yuv转RGB的运算进行了魔改,结果导致精度损失。结果导致 JPG 图片压缩发绿
RGB、YUV各颜色分量的范围为:[0, 1]
RGB --> YUV
Y= 0.299⋅R+0.587⋅G+0.114⋅B U=-0.147⋅R-0.289⋅G+0.436⋅B V= 0.615⋅R-0.515⋅G-0.100⋅B
注:将彩色图像转换成灰色图像,就是从RGB求得Y明亮度的过程 即:灰度 = 0.299⋅R+0.587⋅G+0.114⋅B
YUV --> RGB
R= Y+1.14⋅V G= Y-0.39⋅U-0.58⋅V B= Y+2.03⋅U
# 色彩空间
图像的色彩空間cvtColor(HSV、HSL、HSB ) (opens new window)
色彩空间RGB/CMYK/HSL/HSB/HSV/Lab/YUV基础理论及转换方法:RGB与YUV (opens new window)
RGB色空间是电脑/手机/平板采用的RGB三原色加法色形成的色空间,适用于CG。
CMYK色空间,是印刷油墨CMYK四色减法色形成的色空间,适用于印刷系统。
以上两个色空间,是由显色手段决定的,受到显示器或者印刷手段的材料和工艺的物理限制。
HSB/HSV/HSL则是在RGB的基础上,根据颜色三属性的理论推导出来的色空间,最符合人眼的直觉。
最后的Lab色空间是一个内部转换用的色空间,RGB色转换为CMYK色时,需要先转换为Lab色,再转为CMYK
# RGB
A:透明度(Alpha)
R:红色(Red)
G:绿(Green)
B:蓝(Blue)
Android上:
Bitmap.Config ARGB_8888:由4个8位组成,即A=8,R=8,G=8,B=8,那么****一个像素点占8+8+8+8=32位****(4字节)与下面RGB32相反
Bitmap.Config ARGB_4444:由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 (2字节)
Bitmap.Config RGB_565:没有透明度,R=5,G=6,B=5,,那么一个像素点占5+6+5=16位(2字节)
Bitmap.Config ALPHA_8:每个像素占8位,只有透明度,没有颜色。
BGR: RGB24 opencv常用.
# HSL/HSV
参考: RGB、HSL、Hex 網頁色彩碼,看完這篇全懂了 (opens new window)
#
# 从像素点中提取a,r,g,b色值:
均为遍历Android的bitmap像素
# c实现
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
color = *((int *) pixelColor);
if(isRGB565){
//图像为RGB565时
r = (BYTE) ((color & 0xF800) >> 11 << 3);
g = (BYTE) ((color & 0x07E0) >> 5 << 2);
b = (BYTE) (color & 0x001F << 3);
pixelColor += 2;
} else{
//图像为ARGB888时
r = (BYTE) ((color & 0x00FF0000) >> 16) ;
g = (BYTE) ((color & 0x0000FF00) >> 8) ;
b = (BYTE) (color & 0X000000FF);
pixelColor += 4;
}
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data += 3;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# java实现
out: for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
// The argb {@link Color} at the specified coordinate
int pix = tagBitmap.getPixel(i,j);
long alpha = ((pix >> 24) & 0xff)
int r = ((pix >> 16) & 0xff);
int g = ((pix >> 8) & 0xff);
int b = ((pix ) & 0xff);
2
3
4
5
6
7
8
# alpha通道合成前景色和背景色
常用于png转jpg中
前景色* alpha/255 + 背景色 * (255 - alpha)/255
要使用rgb三个通道分别计算,而不能作为一个int值整体计算:
out: for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
// The argb {@link Color} at the specified coordinate
int pix = tagBitmap.getPixel(i,j);
long alpha = ((pix >> 24) & 0xff)
if(alpha == 0){
//将alpha改成255.完全不透明
pix = luban.tintBgColorIfHasTransInAlpha | 0xff000000;
}else {
/* 要使用rgb三个通道分别计算,而不能作为一个int值整体计算:
long pix2 = (long) (pix * alpha/255f + luban.tintBgColorIfHasTransInAlpha * (255f-alpha) / 255f);
pix2 = pix2 | 0xff000000; */
int r = ((pix >> 16) & 0xff);
int g = ((pix >> 8) & 0xff);
int b = ((pix ) & 0xff);
int br = ((luban.tintBgColorIfHasTransInAlpha >> 16) & 0xff);
int bg = ((luban.tintBgColorIfHasTransInAlpha >> 8) & 0xff);
int bb = ((luban.tintBgColorIfHasTransInAlpha ) & 0xff);
int fr = Math.round((r * alpha + br * (255-alpha)) / 255f);
int fg = Math.round((g * alpha + bg * (255-alpha)) / 255f);
int fb = Math.round((b * alpha + bb * (255-alpha)) / 255f);
// 注意是用或,不是用加: pix = 0xff << 24 + fr << 16 + fg << 8 + fb;
pix = (0xff << 24) | (fr << 16) | (fg << 8) | fb;
//等效: Color.argb(0xff,fr,fg,fb);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 计算一个颜色的反色
主要用于在图片上显示文字,计算出文字用哪种颜色可以看起来反差最大,看起来最明显
首先计算整张图或者文字待显示区域的图像的RGB均值,可以直接用像素点算,也可以使用Android的 Palette API:
使用 Palette API 选择颜色 (opens new window)
得到这个背景RGB后,如何计算其反差色:
使用255-单通道值 肯定不行,如果一个颜色刚好是128 128 128. 计算出来的色值也是基本一样. 并无反差.
HSV/HSL(色相 (opens new window)、饱和度 (opens new window)、明度 (opens new window))是基于人眼感觉的色彩空间表示法,那么使用HSV色彩空间的值来计算?
其实只要针对背景的明度,选择前景色是用白字还是黑字即可:
参考: 计算能在任何背景色上清晰显示的前景色 (opens new window)
/// 获取一个颜色的人眼感知亮度,并以 0~1 之间的小数表示。
/// </summary>
private static double GetGrayLevel(Color color)
{
return (0.299 * color.R + 0.587 * color.G + 0.114 * color.B) / 255;
}
private static Color GetReverseForegroundColor(double grayLevel) => grayLevel > 0.5 ? Colors.Black : Colors.White;
2
3
4
5
6
7
8
9
如果不止要白色黑色,而要绝对的反色呢?
色相取对角线的色相,明度根据背景取0%或100%.饱和度取100%.
# 本质和相关api
像素操作在Android上可以用java/c自己取色计算,实现各种通道混合,以及多图颜色混合.
也可以使用Android自带的一些api,比如Android关于Color你所知道的和不知道的一切 (opens new window)里提到的
colorFilter颜色过滤器,Xfermode:图片叠合时的处理方式 -> 也就是ps里的那个图层间的混合选项:
也可以使用opencv的相关api实现图片混合计算. 本质上都是两幅图像素间的加减乘除运算.
对比Android 绘画和图像相关api,其实和ps有很多相互印证之处. 另有两篇好文可以参考:
可以结合ps一起学
Android关于Color你所知道的和不知道的一切 (opens new window)