图像基本操作
一、图像的算数运算
1.1、加法运算
简单相加:将两副图像简单相加。输入图像与输出图像的深度可以相同也可以不同。例如,可以将CV_16U和CV_8S类型的图像相加,生成一个CV_32F类型的图像。相加后的像素值如果超过上限,则取上限值。
void Core.add(Mat src1, Mat src2, Mat dst)
- src1:输入图像1
- src2:输入图像2
- dst:输出图像,尺寸和通道数与输入图像相同
加权相加:将两幅图像按权重相加,其计算公式为dst=src1aplha+src2beta+gamma
void Core.addWeighted(Mat src1, double appha, Mat src2, double beta, double gamma, Mat dst)
- src1:输入图像1
- alpha:输入图像1的权重
- src2:输入图像2
- beta:输入图像2的权重
- gamma:加权相加后额外添加的值
- dst:输出图像,其尺寸和通道数与输入图像相同
public class Add {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取两幅图像
Mat src1 = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/img1.jpg");
Mat src2 = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/img2.jpg");
//在屏幕上显示
HighGui.imshow("src1", src1);
HighGui.waitKey(0);
HighGui.imshow("src2", src2);
HighGui.waitKey(0);
//加权相加并显示
Mat dst = new Mat();
Core.addWeighted(src1, 0.5, src2, 0.5, 0, dst);
HighGui.imshow("mixed", dst);
HighGui.waitKey(0);
//简单相加并显示
Core.add(src1, src2, dst);
HighGui.imshow("added", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图1:
原图2:
加权相加:加权相加的参数(appha=0.5,beta=0.5,gamma=0)说明这是加权平均,颜色自然比原来的淡。
简单相加:输入图像的3个通道都是8位无符号整数,范围都是0~255,而白色像素值的RGB为[255,255,255],即输入图像中除了中间的条纹外其余区域的3个像素值都是255。由于该函数相加时对相加后超过上限的像素值直接取上限值,因此所有这些区域相加后3个像素值仍然都是255,即白色。
第一张图像的相交区域是红色,像素的RGB值是[255,0,0],第二张图像的相交区域是蓝色,RGB值为[0,0,255],相加之后的值就是[255,0,255],即紫色。
1.2、减法运算
void Core.subtract(Mat src1, Mat src2, Mat dst)
- src1:输入图像一
- src2:输入图像二
- dst:输出图像,(一减二)尺寸和通道与输入图像相同,深度可以不同
public class Subtract {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取两幅图像
Mat src1 = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/leaf.png");
Mat src2 = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/leaf2.png");
//在屏幕上显示
HighGui.imshow("src1", src1);
HighGui.waitKey(0);
HighGui.imshow("src2", src2);
HighGui.waitKey(0);
//相减并显示
Mat dst = new Mat();
Core.subtract(src1, src2, dst);
HighGui.imshow("Subtract", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图1:
原图2:
减法结果:
1.3、点乘运算
点乘即将两个矩阵对应位置的数值相乘。
//矩阵A
a00 a01
a10 a11
//矩阵B
b00 b01
b10 b11
//点乘结果
a00b00 a01b01
a10b10 a11b11
void Core.multiply(Mat src1, Mat src2, Mat dst)
- src1:输入矩阵1
- src2:输入矩阵2,和src1具有相同尺寸和深度
- dst:输出矩阵,和src1具有相同尺寸和深度
public class Multiply {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//填充矩阵1的数据并输出
Mat mat1 = new Mat(9, 9, CvType.CV_8UC1);
byte[] b1 = new byte[]{
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9
};
mat1.put(0, 0, b1);
System.out.println(mat1.dump());
System.out.println();
//填充矩阵2的数据并输出
Mat mat2 = new Mat(9, 9, CvType.CV_8UC1);
byte[] b2 = new byte[] {
1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,
8,8,8,8,8,8,8,8,8,
9,9,9,9,9,9,9,9,9
};
mat2.put(0, 0, b2);
System.out.println(mat2.dump());
System.out.println();
//点乘并输出
Core.multiply(mat1, mat2, mat1);
System.out.println(mat1.dump());
}
}
[ 1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9;
1, 2, 3, 4, 5, 6, 7, 8, 9]
[ 1, 1, 1, 1, 1, 1, 1, 1, 1;
2, 2, 2, 2, 2, 2, 2, 2, 2;
3, 3, 3, 3, 3, 3, 3, 3, 3;
4, 4, 4, 4, 4, 4, 4, 4, 4;
5, 5, 5, 5, 5, 5, 5, 5, 5;
6, 6, 6, 6, 6, 6, 6, 6, 6;
7, 7, 7, 7, 7, 7, 7, 7, 7;
8, 8, 8, 8, 8, 8, 8, 8, 8;
9, 9, 9, 9, 9, 9, 9, 9, 9]
[ 1, 2, 3, 4, 5, 6, 7, 8, 9;
2, 4, 6, 8, 10, 12, 14, 16, 18;
3, 6, 9, 12, 15, 18, 21, 24, 27;
4, 8, 12, 16, 20, 24, 28, 32, 36;
5, 10, 15, 20, 25, 30, 35, 40, 45;
6, 12, 18, 24, 30, 36, 42, 48, 54;
7, 14, 21, 28, 35, 42, 49, 56, 63;
8, 16, 24, 32, 40, 48, 56, 64, 72;
9, 18, 27, 36, 45, 54, 63, 72, 81]
1.4、点除运算
点除即将两个矩阵对应位置的数据相除。
void Core.divide(Mat src1, Mat src2, Mat dsst)
- src1:输入矩阵1
- src2:输入矩阵2,和src1具有相同尺寸和深度
- dst:输出矩阵,和src1具有相同尺寸和深度
public class Divide {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//填充矩阵1的数据并输出
Mat mat1 = new Mat(1, 9, CvType.CV_8UC1);
byte[] b1 = new byte[]{1,2,3,4,5,6,7,8,9};
mat1.put(0, 0, b1);
System.out.println("mat1(CV_8UC1):");
System.out.println(mat1.dump());
System.out.println();
//填充矩阵2的数据并输出
Mat mat2 = new Mat(1, 9, CvType.CV_8UC1);
byte[] b2 = new byte[] {1,1,1,2,2,2,3,3,3};
mat2.put(0, 0, b2);
System.out.println("mat2(CV_8UC1):");
System.out.println(mat2.dump());
System.out.println();
//点除并输出
Mat dst = new Mat();
System.out.println("mat1/mat2(CV_8UC1):");
Core.divide(mat1, mat2, dst);
System.out.println(dst.dump());
//矩阵1转为CV_32FC1并输出
mat1.convertTo(mat1, CvType.CV_32FC1);
System.out.println("mat1(CV_32FC1):");
System.out.println(mat1.dump());
System.out.println();
//矩阵2转为CV_32FC1并输出
mat2.convertTo(mat2, CvType.CV_32FC1);
System.out.println("mat(CV_32FC1):");
System.out.println(mat2.dump());
System.out.println();
//将dst转成CV_32FC1
Core.divide(mat1, mat2, dst);
System.out.println("mat1/mat2(CV_32FC1):");
System.out.println(dst.dump());
}
}
//CV_8UC1,点除返回结果为整数
mat1(CV_8UC1):
[ 1, 2, 3, 4, 5, 6, 7, 8, 9]
mat2(CV_8UC1):
[ 1, 1, 1, 2, 2, 2, 3, 3, 3]
mat1/mat2(CV_8UC1):
[ 1, 2, 3, 2, 2, 3, 2, 3, 3]
//CV_32FC1,点除返回结果为小数
mat1(CV_32FC1):
[1, 2, 3, 4, 5, 6, 7, 8, 9]
mat(CV_32FC1):
[1, 1, 1, 2, 2, 2, 3, 3, 3]
mat1/mat2(CV_32FC1):
[1, 2, 3, 2, 2.5, 3, 2.3333333, 2.6666667, 3]
二、图像的按位运算
2.1、非运算
按位非运算,也叫作反相,就是将像素的颜色变成与原来相反的颜色,相当于照片底片效果。
void Core.bitwise_not(Mat src, Mat dst)
- src:输入图像
- dst:输出图像,和src具有相同的尺寸和数据类型
public class Bitwise_not {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取两幅图像
Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/fish.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//取反操作获得底片效果
Mat dst = new Mat();
Core.bitwise_not(src, dst);
//显示
HighGui.imshow("negative", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
取反后的图:
2.2、与运算
void Core.bitwise_and(Mat src1, Mat src2, Mat dst)
- src1:输入图像1
- src2:输入图像2
- dst:输出图像,和输入图像具有相同的尺寸和数据类型
由于黑色的像素值每位都是0,而0无论与0还是1进行运算都是0,因此图像按位与运算可以产生类似窗口的效果。
public class Bitwise_and {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像为灰度图
Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/fish.png", Imgcodecs.IMREAD_GRAYSCALE);
HighGui.imshow("src", src);
HighGui.waitKey(0);
//创建一个窗口图像,背景为全黑,中央区域有一个白色实心矩形
Scalar black = new Scalar(0,0,0);
Scalar white = new Scalar(255, 255, 255);
Mat window = new Mat(src.size(), CvType.CV_8UC1, black);
Imgproc.rectangle(window, new Point(400, 40), new Point(660, 250), white, -1);
//显示窗口图像
HighGui.imshow("window", window);
HighGui.waitKey(0);
//或操作
Mat dst = new Mat();
Core.bitwise_and(src, window, dst);
//显示
HighGui.imshow("dst", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图读取为灰度图:
窗口图:
结果:
2.3、或运算
void Core.bitwise_or(Mat src1, Mat src2, Mat dst)
- src1:输入图像1
- src2:输入图像2
- dst:输出图像,和输入图像具有相同的尺寸和数据类型
由于白色的像素值每位都是1,而1无论与0还是1进行或运算后都是1,因此图像的按位或运算也可以产生类似窗口的效果。
public class Bitwise_or {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像为灰度图
Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/fish.png", Imgcodecs.IMREAD_GRAYSCALE);
HighGui.imshow("src", src);
HighGui.waitKey(0);
//创建一个窗口图像,背景为全白,中央区域有一个黑色实心矩形
Scalar black = new Scalar(0,0,0);
Mat window = new Mat(src.size(), CvType.CV_8UC1, new Scalar(255));
Imgproc.rectangle(window, new Point(160, 60), new Point(350, 240), black, -1);
//显示窗口图像
HighGui.imshow("window", window);
HighGui.waitKey(0);
//或操作
Mat dst = new Mat();
Core.bitwise_or(src, window, dst);
//显示
HighGui.imshow("dst", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图读取为灰度图:
窗口图:
结果:
2.4、异或运算
void Core.bitwise_xor(Mat src1, Mat src2, Mat dst)
- src1:输入图像1
- src2:输入图像2
- dst:输出图像,和输入图像具有相同的尺寸和数据类型
在进行异或运算时,如果同为0或1,则结果为0,否则结果为1。异或操作有一个特殊之处,就是任意一个数经过两次异或后又会变回这个数本身。
这个性质可以被用来进行图像的加密和解密。将原图像与秘钥图像进行一次按位异或操作后,图像被加密;将加密后的图像与秘钥图像再次进行异或操作后,图像又恢复到加密前的样子,从而实现解密。
public class Bitwise_xor {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取待加密图像并在屏幕显示
Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/church.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
//读取待加密图像并在屏幕显示
Mat key = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/key.jpg");
HighGui.imshow("key", key);
HighGui.waitKey(0);
//按位异或加密图像并在屏幕上显示
Mat dst = new Mat();
Core.bitwise_xor(src, key, dst);
HighGui.imshow("encrypted", dst);
HighGui.waitKey(0);
//再次执行按位异或操作解密图像并在屏幕上显示
Core.bitwise_xor(dst, key, dst);
HighGui.imshow("decrypted", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:
秘钥图:
加密后:
最后异或会再次恢复为原图。
三、图像二值化
在对各种图形进行处理操作的过程中,常常需要对图像中的像素做出取舍,剔除一些低于或者高于一定值的像素。例如,对扫描的书籍进行文字识别时,图像往往是灰度图甚至是彩色图。彩色图可以先转换为灰度图,但即使是8位灰度图也有256个取值,据此来识别太过复杂。此时就要先将灰度图转换为二值图,然后进行识别。
3.1、全局阈值
threshold()函数使用全局阈值,即整幅图像采用同一个阈值。
double Imgproc.threshold(Mat src, Mat dst, double thresh, double maxval, int type)
- src:输入图像,要求为CV_8U或CV_32F类型
- dst:输出图像,和src具有相同的尺寸、数据类型和通道数
- thresh:阈值
- maxval:二值化的最大值,只用于Imgproc.THRESH_BINARY和Imgproc.THRESH_BINARY_INV两种类型
- type:二值化类型,可选参数如下:
- Imgproc.THRESH_BINARY:大于阈值时取maxval,否则取0
- Imgproc.THRESH_BINARY_INV:大于阈值时取0,否则取maxval
- Imgproc.THRESH_TRUNC:大于阈值时为阈值,否则不变
- Imgproc.THRESH_TOZERO:大于阈值时不变,否则取0
- Imgproc.THRESH_TOZERO_INV:大于阈值取0,否则不变
- Imgproc.THRESH_OTSU:大津法自动寻找全局阈值
- Imgproc.THRESH_THIANGLE:三角形法自动寻找全局阈值
其中THRESH_OTSU和THRESH_TRIANGLE是获取阈值的方法,可以和另外5种联用,如Imgproc.THRESH_BINARY|Imgproc.THRESH_OTSU。
如果把输入图像看做连续变化的信号,将二值化的方法看做滤波器,则通过滤波器后的信号如下图:
public class Threshold {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/church.png");
HighGui.imshow("src", src);
HighGui.waitKey(0);
Mat dst = new Mat();
//二值化处理并在屏幕上显示,阈值=90
Imgproc.threshold(src, dst, 90, 255, Imgproc.THRESH_BINARY);
HighGui.imshow("threshold=90", dst);
HighGui.waitKey(0);
//二值化处理并在屏幕上显示,阈值=120
Imgproc.threshold(src, dst, 120, 255, Imgproc.THRESH_BINARY);
HighGui.imshow("threshold=120", dst);
HighGui.waitKey(0);
//二值化处理并在屏幕上显示,阈值=180
Imgproc.threshold(src, dst, 180, 255, Imgproc.THRESH_BINARY);
HighGui.imshow("threshold=180", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图:
阈值为90时的二值图:
阈值为120时的二值图:
阈值为180时的二值图:
由此可见,阈值的设置对结果会有很大的影响,如果是用于文字识别的扫描图片,阈值设置不当则可能使重要信息都是而造成识别失败。
3.2、自适应阈值
有时候使用全局阈值效果并不好,尤其是一张图像上的不同部分的亮度差异较大时。类似情况可以用自适应阈值来解决,图像上的每个小区域都分别计算与其对应的阈值,因此在亮度显著不同的图像上效果较好。
void Imgproc.adaptiveThreshold(Mat src, Mat dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
- src:输入图像,要求是8位单通道图像
- dst:输出图像,尺寸和通道数与src相同
- maxval:二值化的最大值
- adaptiveMethod:自适应确定阈值的方法,可选参数如下:
- Imgproc.ADAPTIVE_THRESH_MEAN_C:阈值取自领域的平均值
- Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C:阈值取自领域的加权和,权重为一个高斯窗口
- thresholdType:阈值类型,只能是以下两种(Threshold()函数的5种阈值类型中的前两种)
- Imgproc.THRESH_BINARY
- Imgproc.THRESH_BINARY_INV
- blockSize:用来计算阈值的领域大小
- C:一个常数,阈值等于计算出来的平均值或者加权平均值减去这个常数
public class AdaptiveThreshold {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并显示
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/church.png", Imgcodecs.IMREAD_GRAYSCALE);
HighGui.imshow("src", src);
HighGui.waitKey(0);
//二值化处理并在屏幕上显示
Mat dst = new Mat();
Imgproc.threshold(src, dst, 127, 255, Imgproc.THRESH_BINARY);
HighGui.imshow("threshold", dst);
HighGui.waitKey(0);
//自适应二值化并在屏幕上显示
Imgproc.adaptiveThreshold(src, dst, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 7, 8);
HighGui.imshow("adaptive", dst);
HighGui.waitKey(0);
//读取图像并显示
Mat fish = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/fish.png", Imgcodecs.IMREAD_GRAYSCALE);
HighGui.imshow("fish", fish);
HighGui.waitKey(0);
//二值化处理并在屏幕上显示
Imgproc.threshold(fish, dst, 127, 255, Imgproc.THRESH_BINARY);
HighGui.imshow("threshold", dst);
HighGui.waitKey(0);
//自适应二值化并在屏幕上显示
Imgproc.adaptiveThreshold(fish, dst, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 7, 8);
HighGui.imshow("adaptive", dst);
HighGui.waitKey(0);
System.exit(0);
}
}
原图像1:
图像1二值化:
图像1自适应处理:
原图像2:
图像2二值化:
图像2自适应处理:
3.3、查找表
对图像进行二值化的threshold()函数只能设定一个阈值,但有时需要与多个阈值进行比较,此时就要用到查找表(Look-Up-Table,LUT)。LUT其实就是一张对照表,将一像素值映射为另外一像素值,如下图:
原像素值 0 1 2 3 4 5 6 7 8 9 ... 252 253 254 255
映射后值 0 0 0 0 1 1 1 1 2 2 ... 63 63 63 63
void Core.LUT(Mat src, Mat lut, Mat dst)
- src:8位输入矩阵
- lut:有256个元素的查找表。如果输入矩阵是多通道,则查找表必须是单通道(查找表适用于所有通道)或与输入图像有相同的通道数
- dst:输出矩阵,与src具有相同的尺寸和通道数,深度与lut相同
LUT工作原理示例:
public class Lut {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//矩阵Mat的初始值
byte[] b1 = new byte[]{
1, 2, 3, 4, 5,
2, 2, 3, 4, 5,
3, 2, 3, 4, 5,
4, 2, 3, 4, 5,
5, 2, 3, 4, 5
};
//创建5*5单通道矩阵
Mat src = new Mat(5, 5, CvType.CV_8U);
src.put(0, 0, b1);
//创建查找表,初始值为1
Mat lut = new Mat(new Size(256, 1), CvType.CV_8U, new Scalar(1));
//修改查找表前10个数字的值
byte[] b2 = new byte[]{0, 11, 22, 33, 44, 55, 66, 77, 88, 99};
lut.put(0, 0, b2);
//用查找表里的值填充输出矩阵
Mat dst = new Mat();
Core.LUT(src, lut, dst);
//控制台输出原矩阵、查找表矩阵和输出矩阵
System.out.println("src:");
System.out.println(src.dump());
System.out.println();
System.out.println("lut:");
System.out.println(lut.dump());
System.out.println();
System.out.println("dst:");
System.out.println(dst.dump());
}
}
src:
[ 1, 2, 3, 4, 5;
2, 2, 3, 4, 5;
3, 2, 3, 4, 5;
4, 2, 3, 4, 5;
5, 2, 3, 4, 5]
lut:
[ 0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
dst:
[ 11, 22, 33, 44, 55;
22, 22, 33, 44, 55;
33, 22, 33, 44, 55;
44, 22, 33, 44, 55;
55, 22, 33, 44, 55]
LUT处理图像:
public class Lut2 {
static {
OpenCV.loadLocally(); // 自动下载并加载本地库
}
public static void main(String[] args) {
//读取图像并转换为灰度图
Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/butterfly.png");
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY);
//在屏幕上显示图像灰度图
HighGui.imshow("src", src);
HighGui.waitKey(0);
//创建查找表
Mat lut = new Mat(new Size(256, 1), CvType.CV_8U);
//设置查找表,将灰度值分成8个层级,总体亮度减半
byte[] i = new byte[256];
for (int n = 0; n < 256; n++) {
byte b = (byte)(n/32);
i[n] = (byte)(b * 32);
}
lut.put(0, 0, i);
//用查找表里的值填充输出矩阵
Mat dst = new Mat();
Core.LUT(src, lut, dst);
//在控制台输出替换后的图像
HighGui.imshow("After Lut", dst);
HighGui.waitKey(0);
//对替换后的图像进行自适应二值化处理并显示
int adaptiveMethod = Imgproc.ADAPTIVE_THRESH_MEAN_C;
int thresholdType = Imgproc.THRESH_BINARY;
Imgproc.adaptiveThreshold(dst, dst, 255, adaptiveMethod, thresholdType, 7, 8);
HighGui.imshow("AfterLut Adaptive", dst);
HighGui.waitKey(0);
//对原图像进行自适应二值化处理并显示
Imgproc.adaptiveThreshold(src, dst, 255, adaptiveMethod, thresholdType, 7, 8);
HighGui.imshow("Src Adaptive", dst);
HighGui.waitKey(0);
System.exit(0);
}
该程序将灰度值分为8段,每32个值为一段,因而查找表操作后图像的连续性明显不如原图。为了更明显地比较两者的不同,还对应用查找表后的图像进行了自适应二值化处理,对照图像可以发现二者有明显的差异,特别是背景部分。
原图:
LUT处理后:
LUT处理后自适应二值化:
原图自适应二值化: