位图模式是色彩模式中占有空间最小,包含信息量最小的一种模式。它只有黑白两种色素,因此也叫做黑白图。
从彩色图转为位图的过程被称为 “二值化”。彩色图不能直接转为位图,需要先将其转为灰度模式,然后才能转为位图。
灰度模式是采用单一色调来表示图像。1个像素点占1字节,即 8 bit,每个像素使用 0~255 来表示亮度,0为黑色,128为灰色,255为白色。
RGB模式是我们最常用到的,也被称为真色彩。RGB模式的图像有3个颜色通道,分别是 红(Red),绿(Green)和 蓝(Blue),每个通道占用 8 bit,值范围为 0~255
CMYK也称作印刷色彩模式。它和RGB相比最大不同是,RGB模式是发光的色彩模式,你在一间黑暗的房间内仍然可以看见萤幕上的内容。
CMYK是一种依靠反光的色彩模式,我们能阅读报纸的内容是为什么呢?是因阳光或灯光照射到报纸上,再把内容反射到我们的眼中。CMYK是需要有外界光源的情况下才可以看到的。所以在黑暗房间内是无法阅读的。
从理论上讲,只需要CMY三种油墨就足够,把CMY加在一起就应该得到黑色。但是高纯度的油墨暂时还不能实现,CMY相加的结果是暗红色。因此,为了确保黑色的输出,还需要加入一种专门的黑墨来调和。
在 HSB 模式中,H(Hues)表示色相(0 ~ 360),S(Saturation)表示饱和度(0 ~ 100%),B(brightness)表示亮度(0 ~ 100%)。
HSB模式对应的媒介是人眼。
HSB模式中S和B呈现的数值越高,饱和度明度越高,页面色彩强烈艳丽,对视觉刺激是迅速的,醒目的效果,但不益于长时间的观看。
YUV开始主要用于电视系统以及模拟视频领域,后面手机相机也延用了这一格式,因此大部分摄像头输出的图片格式都是 YUV 格式(安卓是NV12,YUV的一种)。YUV 中的 “Y” 表示亮度(Luminance 或 Luma)也就是灰度值,“U” 和 “V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。如果只用 “Y” 而不用 “UV” 也可也进行呈像,只不过是黑白的,这样就能很好的解决了黑白电视与彩色电视的兼容问题。此外,与RGB相比,YUV具有占用带宽更少的优势。
Mat本质上是由两个数据部分组成的类: (包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等) 的矩阵头和一个指针,指向包含了像素值的矩阵(可根据选择用于存储的方法采用任何维度存储数据)。矩阵头部的大小是恒定的。然而,矩阵本身的大小因图像的不同而不同,通常是较大的数量级。
注意到一点,Mat 对于无法保存透明度通道,PNG 中透明的像素点会将其转为黑色
Mat 是一个 C/C++ 的指针类型,因此它需要通过手动分配空间和释放空间,对应的函数为 new Mat()
和 mat.release()
/**
* Short form of the bitmapToMat(bmp, mat, unPremultiplyAlpha=false).
* @param bmp is a valid input Bitmap object of the type 'ARGB_8888' or 'RGB_565'.
* @param mat is a valid output Mat object, it will be reallocated if needed, so Mat may be empty.
*/
public static void bitmapToMat(Bitmap bmp, Mat mat) {
bitmapToMat(bmp, mat, false);
}
/**
* Converts OpenCV Mat to Android Bitmap.
*
*
This function converts an image in the OpenCV Mat representation to the Android Bitmap.
*
The input Mat object has to be of the types 'CV_8UC1' (gray-scale), 'CV_8UC3' (RGB) or 'CV_8UC4' (RGBA).
*
The output Bitmap object has to be of the same size as the input Mat and of the types 'ARGB_8888' or 'RGB_565'.
*
This function throws an exception if the conversion fails.
*
* @param mat is a valid input Mat object of types 'CV_8UC1', 'CV_8UC3' or 'CV_8UC4'.
* @param bmp is a valid Bitmap object of the same size as the Mat and of type 'ARGB_8888' or 'RGB_565'.
* @param premultiplyAlpha is a flag, that determines, whether the Mat needs to be converted to alpha premultiplied format (like Android keeps 'ARGB_8888' bitmaps); the flag is ignored for 'RGB_565' bitmaps.
*/
public static void matToBitmap(Mat mat, Bitmap bmp, boolean premultiplyAlpha) {
if (mat == null)
throw new IllegalArgumentException("mat == null");
if (bmp == null)
throw new IllegalArgumentException("bmp == null");
nMatToBitmap2(mat.nativeObj, bmp, premultiplyAlpha);
}
但在图像的处理过程中却很少使用 YUV 格式,一般都需要转成 RGB 格式 或者灰度图格式进行转换。
opencv 提供了 cvtColor 函数,用于在图像的不同色彩空间进行转换。
函数被定义在 Imgproc.java 中,
public static void cvtColor(Mat src, Mat dst, int code) { …… }
下面的代码从网络上加载一幅图片,然后使用 opencv 的颜色空间转换函数 cvtColor,将其由 “彩色” 转为 “灰度图”
public class MainActivity extends AppCompatActivity {
private String TAG = "CvtColor";
private String imgUrl = "https://www.baidu.com/img/flexible/logo/pc/result.png";
private ImageView imageView1;
private ImageView imageView2;
private ImageView imageView3;
private Button opencvBtn;
private Button resetBtn;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gray);
initView();
resetImg();
// OpenCVLoader.initDebug 是用来加载 OpenCV 的 so 库的,so库如果全部加载成功,则返回 true
if (OpenCVLoader.initDebug()) {
opencvBtn.setOnClickListener(v -> {
convert2Grey();
});
resetBtn.setOnClickListener(v -> {
resetImg();
});
} else {
Log.i(TAG, "OpenCVLoader.initDebug fail");
}
}
private void initView() {
imageView1 = findViewById(R.id.imageView1);
imageView2 = findViewById(R.id.imageView2);
imageView3 = findViewById(R.id.imageView3);
opencvBtn = findViewById(R.id.opencvBtn);
resetBtn = findViewById(R.id.resetBtn);
}
private void convert2Grey() {
if(bitmap == null) return;
// Mat是OpenCV的一种图像格式
Mat src = new Mat();
Mat dst1 = new Mat();
Mat dst2 = new Mat();
// 将 bitmap 进行深拷贝,格式为 ARGB,包含透明度通道
Bitmap bitmap1 = bitmap.copy(Bitmap.Config.ARGB_8888, true),
bitmap2 = bitmap.copy(Bitmap.Config.ARGB_8888, true),
bitmap3 = bitmap.copy(Bitmap.Config.ARGB_8888, true);
// 利用 OpenCV 的工具类,将 bitmap 转为 OpenCV 的 Mat 类
Utils.bitmapToMat(bitmap1, src);
// 使用 OpenCV 的 cvtColor 进行颜色空间转换,将 "红"、"蓝" 通道互换
Imgproc.cvtColor(src, dst1, Imgproc.COLOR_RGBA2BGR);
// 使用 OpenCV 的cvtColor 进行颜色空间转换,将 RGB 图转为灰度图
Imgproc.cvtColor(src, dst2, Imgproc.COLOR_RGB2HSV);
// 利用 OpenCV 的工具类,将 Mat 转为 bitmap
Utils.matToBitmap(dst1, bitmap2);
imageView2.setImageBitmap(bitmap2);
Utils.matToBitmap(dst2, bitmap3);
imageView3.setImageBitmap(bitmap3);
src.release();
dst1.release();
dst2.release();
}
// 从网络中加载 bitmap 图像
public void resetImg() {
new Thread(() -> {
HttpURLConnection httpURLConnection = null;
try {
URL url = new URL(imgUrl);
httpURLConnection = ((HttpURLConnection)url.openConnection());
int responseCode = httpURLConnection.getResponseCode();
if (responseCode != 200) {
throw new IOException("图片文件不存在或路径错误,错误代码:" + responseCode);
}
bitmap = BitmapFactory.decodeStream(url.openStream());
runOnUiThread(()-> {
imageView1.setImageBitmap(bitmap);
imageView2.setImageBitmap(null);
imageView3.setImageBitmap(null);
});
} catch (IOException e) {
e.printStackTrace();
}
if(httpURLConnection != null) httpURLConnection.disconnect();
}).start();
}
}