OpenCV 默认通道模型是BGR(蓝绿红)
S = 有符号整型
U = 无符号整型
F = 浮点型
CV_8U - 8位无符号整数(0…255)
CV_8S - 8位有符号整数(-128…127)
CV_16U - 16位无符号整数(0…65535)
CV_16S - 16位有符号整数(-32768…32767)
CV_32S - 32位有符号整数(-2147483648…2147483647)
CV_32F - 32位浮点数(-FLT_MAX…FLT_MAX,INF,NAN)
CV_64F - 64位浮点数(-DBL_MAX…DBL_MAX,INF,NAN)
CV_8UC3而后面的C1、C2、C3是什么意思呢?
这里的1、2、3代表的是通道数,比如RGB就是3通道,颜色表示最大为255,所以可以用CV_8UC3这个数据类型来表示;灰度图就是C1,只有一个通道;而带alph通道的PNG图像就是C4,是4通道图片。
如果矩阵是类型:CV_8U 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_8S 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_16U 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_16S 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_32S 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_32F 则使用 : Mat.at(y,x)
如果矩阵是类型:CV_64F 则使用 : Mat.at(y,x)
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
//创建16位无符号整形,每个元素三个通道为1,5,6
Mat f(4, 5, CV_16UC3, Scalar(1, 5, 6, 9));
std::cout << f << std::endl;
/*
[1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6;
1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6;
1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6;
1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6, 1, 5, 6]
*/
//获取列数
std::cout << f.cols << std::endl;//5
//获取行数
std::cout << f.rows << std::endl;//4
//获取一行的最大字节数 CV_16UC3 = 2*3=6 6*5=30
std::cout << f.step << std::endl;//30
//获取每个元素的字节数 CV_16UC3 = 2*3=6
std::cout << f.elemSize() << std::endl;//6
//获取矩阵中元素的个数 4*5 = 20
std::cout << f.total() << std::endl;//20
//获取通道数
std::cout << f.channels() << std::endl;//3
//Vec3s 获取(0,0)为第一个元素,为3通道
cv::Vec3s vc3 = f.at<cv::Vec3s>(0, 0);
//获取元素中第一个通道值
std::cout << (int)vc3.val[0] << std::endl;
/*用数据流指针获取通道值
f.data:数据存储的起始地址 (uchar*类型);
step的几个类别区分:
step:矩阵第一行元素的字节数
step[0]:矩阵第一行元素的字节数
step[1]:矩阵中一个元素的字节数
step1(0):矩阵中一行有几个通道数
step1(1):一个元素有几个通道数(channel())
*/
std::cout << "通道1:" << (int)*(f.data + f.step[0] * 2 + f.step[1] * 2 + 0) << std::endl;
std::cout << "通道2:" << (int)*(f.data + f.step[0] * 2 + f.step[1] * 2 + 2) << std::endl;
std::cout << "通道3:" << (int)*(f.data + f.step[0] * 2 + f.step[1] * 2 + 4) << std::endl;
//创建一行5列单通道数据 枚举
Mat a = (cv::Mat_<int>(1, 5) << 1, 2, 3, 4, 5);
std::cout << a << std::endl;//[1, 2, 3, 4, 5]
//向量生成对角线矩阵
Mat b = Mat::diag(a);
std::cout << b << std::endl;
/*
[1, 0, 0, 0, 0;
0, 2, 0, 0, 0;
0, 0, 3, 0, 0;
0, 0, 0, 4, 0;
0, 0, 0, 0, 5]
*/
//抠图或者裁剪 获取第2,3行和第3,4列相交 的数据
//按照矩阵中的元素抠图
Mat c = Mat(b, Range(2, 4),Range(3,5));
std::cout << c << std::endl;
/*
[0, 0;
4, 0]
*/
//生成5行6列,对角线为1的数据,每个元素为16位
Mat d = Mat::eye(5,6,CV_16U);
std::cout << d << std::endl;
/*
[1, 0, 0, 0, 0, 0;
0, 1, 0, 0, 0, 0;
0, 0, 1, 0, 0, 0;
0, 0, 0, 1, 0, 0;
0, 0, 0, 0, 1, 0]
*/
//生成5行6列,全部元素为1,每个元素为16位
Mat e = Mat::ones(5, 6, CV_16U);
std::cout << e << std::endl;
/*
[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]
*/
//生成5行6列,全部元素为0,每个元素为16位
Mat g = Mat::zeros(5, 6, CV_16U);
std::cout << g << std::endl;
/*
[0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0]
*/
设为A,B为Mat类型,s是Scalar类型,a是一个实数。下面列出关于Mat的常用运算:
矩阵加减: A+B,A-B,A+s,A-s,s+A,s-A,-A.
矩阵乘以实数: A*a,a*A
逐元素乘除: A.mul(B),A/B,a/A
矩阵乘法: A*BmaxVal; Point minPos,m
矩阵倒置: A.t()
矩阵的逆: A.inv()
矩阵比较: A comp B,A comp a,a comp A。这里comp包括 >, >=,==,!=,<=,<。得出的结果是一个单通道8位的矩阵,元素的值为255或0。
矩阵位操作: A logic B, A logic s,s logic A。这里logic包括:&,|,^
向量的差乘和内积: A.cross(B),A.dot(B);
视频打开读取
VideoCapture video;
video.open("H:/OBS录像地址/2023-03-25_21-08-01.mp4");
if (!video.isOpened())
{
std::cout << "视频打开失败。。。" << std::endl;
return -1;
}
cout << "视频帧率=" << video.get(CAP_PROP_FPS);
cout << "视频宽度=" << video.get(CAP_PROP_FRAME_WIDTH);
while (true)
{
Mat frame;
//读取下一帧图像
video >> frame; // == video.read(frame);
if (frame.empty())
{
break;
}
namedWindow("video", WINDOW_NORMAL);
imshow("video", frame);
uchar c = waitKey(1000 / video.get(CAP_PROP_FPS));
if (c == 'q')
{
break;
}
}
//释放所有打开的窗口
destroyAllWindows();
摄像头打开读取
VideoCapture video(0);
if (!video.isOpened())
{
std::cout << "摄像头打开失败。。。" << std::endl;
return -1;
}
Mat image;
//获取图像
video >> image;
//判断图像是否读取成功
if (image.empty())
{
std::cout << "图像获取失败。。。" << std::endl;
return -1;
}
//判断相机(视频)类型是否为彩色
bool isColor = (image.type() == CV_8UC3);
VideoWriter writer;
//设置编码格式
int codec = VideoWriter::fourcc('M','P','4','2');
//设置视频帧率fps
double fps = 15.0;
//保存文件名
string filename = "shexiangtou.mp4";
writer.open(filename, codec, fps, image.size(), isColor);
if (!writer.isOpened())
{
cout << "打开视频文件失败。。。";
return -1;
}
while (true)
{
//判断摄像头能够继续读出一帧图像
if (!video.read(image))
{
cout << "摄像头断开连接或者视频读取完成。。。" << endl;
break;
}
writer.write(image);
namedWindow("video", WINDOW_NORMAL);
imshow("video", image);
uchar c = waitKey(1000 / fps);
if (c == 'q')
{
break;
}
}
//关闭视频流
video.release();
writer.release();
//释放所有打开的窗口
destroyAllWindows();
RGB(红绿蓝)和HSV(色相饱和度亮度)是两种常用的颜色模型,用于描述和表示颜色。
RGB模型是将颜色表示为红色、绿色和蓝色三个分量的组合。每个分量的取值范围是0到255,表示颜色的强度。通过调整这三个分量的数值,可以生成不同的颜色。例如,纯红色可以表示为RGB(255, 0, 0),纯绿色为RGB(0, 255, 0),纯蓝色为RGB(0, 0, 255)。通过组合不同的RGB值,可以创建出各种各样的颜色。
HSV模型将颜色表示为色相(Hue)、饱和度(Saturation)和亮度(Value)三个分量。色相表示颜色在色轮上的位置,取值范围通常是0到360度,对应于不同的颜色。饱和度表示颜色的纯度或深浅程度,取值范围是0到1,其中0表示灰色,1表示饱和的纯色。亮度表示颜色的明暗程度,取值范围也是0到1,其中0表示黑色,1表示最亮的颜色。
H——Hue即色相,就是我们平时所说的红、绿,如果你分的更细的话可能还会有洋红、草绿等等;在HSV模型中,用度数来描述色相,其中红色对应0度,绿色对应120度,蓝色对应240度。
S——Saturation即饱和度,色彩的深浅度(0-100%) ,对于一种颜色比如红色,我们可以用浅红——大红——深红——红得发紫等等语言来描述它(请原谅一个纯理科生的匮乏的颜色系统),对应在画水彩的时候即一种颜料加上不同分量的水形成不同的饱和度。
V——Value即色调,纯度,色彩的亮度(0-100%) ,这个在调整屏幕亮度的时候比较常见。
HSV模型相对于RGB模型更加直观和易于使用,因为它可以更好地描述颜色的特征,例如颜色的明暗度和饱和度。在图像处理、计算机图形学和颜色选择等领域,HSV模型常常用于调整和控制颜色。
如果您想手动将HSV颜色值赋给一个OpenCV的Mat对象,可以按照以下步骤进行操作:
#include
int main()
{
// 创建一个3通道的Mat对象来存储BGR颜色
cv::Mat bgrImage(1, 1, CV_8UC3);
// 设置HSV颜色值
int h = 120; // 色调 (0-179)
int s = 255; // 饱和度 (0-255)
int v = 255; // 明度 (0-255)
// 将HSV颜色值赋给Mat对象
bgrImage.at<cv::Vec3b>(0, 0) = cv::Vec3b(h / 2, s, v);
// 将颜色值从HSV转换为BGR
cv::cvtColor(bgrImage, bgrImage, cv::COLOR_HSV2BGR);
// 显示BGR颜色
cv::imshow("BGR Color", bgrImage);
cv::waitKey(0);
return 0;
}
// 设置HSV颜色值
int h = 120; // 色调 (0-179)
int s = 255; // 饱和度 (0-255)
int v = 255; // 明度 (0-255)
对于HSV颜色空间中的色调(H),OpenCV使用范围为0到179,而不是0到360。在将HSV颜色转换为BGR颜色时,需要将H值除以2。
在OpenCV中,当我们使用8位无符号整数(`CV_8U`)来表示像素值时,通常将其缩放到0到255范围内。这是因为8位无符号整数的范围是0到255,所以将0到1的浮点数映射到这个范围内。
OpenCV是一个流行的计算机视觉库,提供了丰富的图像处理和分析功能。split
函数是OpenCV中的一个函数,用于将多通道图像拆分为各个通道的单通道图像。
以下是split
函数的详细解释和用法示例:
函数原型:
void split(InputArray src, OutputArrayOfArrays dst);
参数:
src
:输入的多通道图像(例如CV_8UC3
类型的图像)。dst
:输出的单通道图像数组,用于存储拆分后的各个通道。说明:
split
函数将输入的多通道图像分割为各个通道,并将每个通道保存在输出数组中。输出数组的大小和类型与输入图像的通道数相匹配。例如,对于3通道的输入图像,输出数组将包含3个单通道图像。
在OpenCV中,merge函数用于将多个单通道图像合并为一个多通道图像。以下是merge函数的详细解释和示例代码(C++):
函数原型:
void merge(const std::vector<cv::Mat>& mv, cv::OutputArray dst);
参数说明:
mv
:包含多个输入单通道图像的向量。图像必须具有相同的尺寸和深度。dst
:输出的多通道图像。
Mat img = imread("D:/OpenCV4.5.1/opencv/sources/doc/js_tutorials/js_assets/lena.jpg");
//imshow("mat", mat);
Mat img32;
//图像转换CV_32F储存
img.convertTo(img32, CV_32F, 1 / 255.0, 0);
Mat gray0, gray1;
//颜色转换
cvtColor(img, gray0, COLOR_BGR2GRAY);
cvtColor(img, gray1, COLOR_RGB2GRAY);
Mat imgs[3];
//分割三通道图像 按照BGR形式分割
split(img, imgs);
Mat imgB = imgs[0];
Mat imgG = imgs[1];
Mat imgR = imgs[2];
Mat imgH;
//合并三通道为imgH
merge(imgs, 3, imgH);
//生成Size(宽,高) 为0的单通道矩阵
Mat zero = Mat::zeros(Size(img.cols, img.rows), CV_8UC1);
vector<Mat> imgsV;
imgsV.push_back(imgB);
imgsV.push_back(imgG);
imgsV.push_back(imgR);
Mat imgsVH;
//合并三通道为imgH
merge(imgsV, imgsVH);
//创建一个通道矩阵都为1
Mat a(4, 5, CV_8UC1, Scalar(1));
//创建一个掩码通道矩阵都为2
Mat b(4, 5, CV_8UC1, Scalar(2));
//把掩码矩阵第一个位置设置为0
*b.data = 0;
double aa, bb;
Point cc, dd;
//aa = 1, bb =1, cc(x=1,y=0),dd(x=1,y=0)
minMaxLoc(a, &aa, &bb, &cc, &dd,b);
在OpenCV中,minmaxloc函数用于在图像中查找最小和最大像素值及其位置。该函数的第六个参数是一个可选的掩码图像,用于指定在哪些像素上执行最小/最大值查找。
具体来说,掩码图像是一个与源图像具有相同尺寸和数据类型的图像,其中每个像素的值都是0或非零。在执行最小/最大值查找时,仅考虑掩码图像中像素值为非零的位置。
例如,如果您有一个源图像和一个掩码图像,其中掩码图像在一些区域中具有非零像素值,那么当您调用minmaxloc函数时,它将仅在掩码图像中像素值为非零的位置上执行最 小/最大值查找。
这可以用于实现各种功能,例如在ROI(感兴趣区域)内查找最小/最大值,或在二进制掩码图像中查找最小/最大值。
threshold
函数是OpenCV中用于图像二值化的函数之一。它可以将灰度图像转换为二值图像,使得图像上的某些区域像素值为0(黑色),而另一些区域像素值为255(白色)。下面是threshold
函数的详细解释:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
参数解释:
src
:输入图像,可以是单通道的灰度图像或多通道的彩色图像。dst
:输出二值化图像,与输入图像具有相同的大小和类型。thresh
:阈值,用于将像素分为两类的分割值。根据不同的类型(type
)来确定阈值的使用方式。maxval
:当像素值超过阈值时,要为其赋予的最大值。通常为255(白色)。type
:二值化类型,指定如何根据阈值对像素进行分割。有多种可选类型。常见的type
参数取值包括:
THRESH_BINARY
:对于输入图像中的每个像素,如果其值大于阈值,则将输出像素设置为maxval
,否则设置为0。THRESH_BINARY_INV
:与THRESH_BINARY
相反,对于输入图像中的每个像素,如果其值大于阈值,则将输出像素设置为0,否则设置为maxval
。THRESH_TRUNC
:对于输入图像中的每个像素,如果其值大于阈值,则将输出像素设置为阈值,否则保持原始像素值不变。THRESH_TOZERO
:对于输入图像中的每个像素,如果其值大于阈值,则保持原始像素值不变,否则设置为0。THRESH_TOZERO_INV
:与THRESH_TOZERO
相反,对于输入图像中的每个像素,如果其值大于阈值,则设置为0,否则保持原始像素值不变。返回值:threshold
函数的返回值为实际使用的阈值。
threshold 函数对于最大值maxval用不到的时候,就算设置了,也不做运算。
blockSize 这个参数怎么理解,请用字符绘制方便理解的图形?
blockSize 参数代表的是每个像素周围用来计算阈值的邻域大小。邻域的大小应该是一个奇数,例如 3、5、7、9 等。
下面是一个示例图形,用字符绘制了一个 5x5 的邻域:
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | X | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
在这个示例中,X 表示中心像素,周围 5 个像素组成了一个 5x5 的邻域。当 blockSize 参数为 5 时,算法就会以这个邻域为基础来计算中心像素的阈值。
在实际应用中,可以通过调整 blockSize 参数来控制阈值计算的精度和速度。如果 blockSize 参数太小,算法可能会受到噪声的干扰,导致分割不准确;
如果 blockSize 参数太大,算法的计算时间会变长,可能会降低算法的实时性。因此,需要根据具体的应用场景来选择合适的 blockSize 参数。
>>blockSize 为3,那么坐标(0,0)第一个像素怎么计算的
当 blockSize 为 3 时,以坐标为(0,0)的像素为例,该像素的邻域大小为 3x3,其邻域内包括了 9 个像素,如下所示:
+---+---+---+
| X | | |
+---+---+---+
| | | |
+---+---+---+
| | | |
+---+---+---+
为了计算该像素的阈值,我们需要将这 9 个像素的灰度值进行求和,并除以 9 得到邻域内像素的平均灰度值 T。
然后,我们根据参数 C 计算该像素的阈值,例如如果 C 为 5,则阈值为 T - C。最后,我们将该像素的灰度值与阈值进行比较,
如果像素的灰度值大于阈值,则将其设置为前景像素,否则将其设置为背景像素。
需要注意的是,对于位于图像边缘的像素,可能无法找到以其为中心的大小为 blockSize 的邻域,此时可以使用填充方式来处理。
例如,可以使用图像边缘像素的值来填充邻域的边缘,以保证每个像素都能计算出其阈值。
Mat img = imread("D:/OpenCV4.5.1/opencv/sources/doc/js_tutorials/js_assets/lena.jpg");
if (img.empty())
{
cout << "图像打开失败。。。" << endl;
return -1;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
Mat img_B, img_BV, gray_B, gray_BV, gray_T, gray_TV, gray_TRUNC;
// threshold 函数对于最大值maxval用不到的时候,就算设置了,也不做运算。
//彩色图像二值化,规则:THRESH_BINARY
threshold(img, img_B, 125, 255, THRESH_BINARY);
threshold(img, img_BV, 125, 255, THRESH_BINARY_INV);
//灰度图像二值化 ,规则:THRESH_BINARY
threshold(gray, gray_B, 125, 255, THRESH_BINARY);
threshold(gray, gray_BV, 125, 255, THRESH_BINARY_INV);
//灰度图像二值化 ,规则:THRESH_TOZERO
threshold(gray, gray_T, 125, 255, THRESH_TOZERO);
threshold(gray, gray_TV, 125, 255, THRESH_TOZERO_INV);
//灰度图像TRUNC变换
threshold(gray, gray_TRUNC, 125, 255, THRESH_TRUNC);
//灰度图形大津法和三角形法二值化
Mat img_O,img_thr_O, img_T, img_thr_T;
threshold(gray, img_thr_O, 125, 255, THRESH_BINARY | THRESH_OTSU);
threshold(gray, img_thr_T, 125, 255, THRESH_BINARY | THRESH_TRIANGLE);
threshold(gray, img_O, 125, 255, THRESH_OTSU);
threshold(gray, img_T, 125, 255, THRESH_TRIANGLE);
//自适应二值化 均值法和高斯法
Mat adaptive_mean, adaptive_gauss;
adaptiveThreshold(gray, adaptive_mean, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 55, 0);
adaptiveThreshold(gray, adaptive_gauss, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 0);
`LUT`是OpenCV中的一个函数,用于对图像进行查表操作(Look-Up Table)。它可以根据一个查找表(Look-Up Table)对输入图像中的每个像素进行映射,从而得到一个输出图像。
以下是`LUT`函数的详细说明:
```cpp
void cv::LUT(InputArray src, InputArray lut, OutputArray dst)
参数:
src
:输入图像,可以是单通道或多通道的图像。lut
:查找表,可以是一个一维数组(cv::Mat
类型),也可以是一个与输入图像通道数和深度相匹配的多维数组。查找表的数据类型可以是整型或浮点型。dst
:输出图像,与输入图像具有相同的大小和类型。功能:
LUT
函数使用查找表对输入图像的每个像素进行映射,并将结果存储在输出图像中。对于输入图像中的每个像素值src(x,y)
,函数会查找查找表中对应索引位置的值,并将其赋值给输出图像的对应位置dst(x,y)
。
查找表的大小应该与输入图像的深度相匹配。对于8位深度的图像,查找表应该是一个256元素的一维数组;对于16位深度的图像,查找表应该是一个65536元素的一维数组。
LUT函数要求LUT参数必须满足以下条件:
1:必须是1通道或者与输入图像的通道数相同的通道数。
2:必须包含256个连续的元素,即必须是一个大小为1x256的连续矩阵。
3:元素类型必须是CV_8U或CV_8S。
代码演示:
uchar lut1[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
{
lut1[i] = 0;
}
if (i > 100 &&i<=200)
{
lut1[i] = 100;
}
if (i > 200)
{
lut1[i] = 255;
}
}
//LUT查找表第一层
Mat lutOne(1, 256, CV_8UC1, lut1); // 修改为1x256大小
uchar lut2[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
{
lut2[i] = 0;
}
if (i > 100 && i <= 150)
{
lut2[i] = 100;
}
if(i > 150 && i <= 200)
{
lut2[i] = 150;
}
if (i > 200)
{
lut2[i] = 255;
}
}
//LUT查找表第二层
Mat lutTwo(1, 256, CV_8UC1, lut2); // 修改为1x256大小
uchar lut3[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
{
lut3[i] = 100;
}
if (i > 100 && i <= 200)
{
lut3[i] = 200;
}
if (i > 200)
{
lut3[i] = 255;
}
}
//LUT查找表第三层
Mat lutThree(1, 256, CV_8UC1, lut3); // 修改为1x256大小
vector<Mat> mergeMats;
mergeMats.push_back(lutOne);
mergeMats.push_back(lutTwo);
mergeMats.push_back(lutThree);
Mat LutTree;
merge(mergeMats, LutTree);
Mat img = imread("D:/OpenCV4.5.1/opencv/sources/doc/js_tutorials/js_assets/lena.jpg");
if (img.empty())
{
cout << "图像打开失败。。。" << endl;
return -1;
}
Mat gray, out0, out1, out2;
cvtColor(img, gray, COLOR_BGR2GRAY);
LUT(gray, lutOne, out0);
LUT(img, lutOne, out1);
LUT(img, LutTree, out2);
imshow("out0", out0);
imshow("out1", out1);
imshow("out2", out2);