|
|
|
|
#include "stdafx.h"
|
|
|
|
|
#include "histProject.h"
|
|
|
|
|
#include "omp.h" // <20><><EFBFBD>˱<EFBFBD><CBB1><EFBFBD>
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD>ܣ<EFBFBD>ֱ<EFBFBD><D6B1>ͼͶӰ<CDB6><D3B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD>Աȶ<D4B1>
|
|
|
|
|
// <20><><EFBFBD>룺
|
|
|
|
|
// src<72><63> <20><><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC>
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
// dst: <20><><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC>
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD>true<75><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD>false
|
|
|
|
|
bool HP(cv::Mat& dst, const cv::Mat &src)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD>֤
|
|
|
|
|
if (src.empty() == true || src.type() != 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>ֱ<EFBFBD><D6B1>ͼ
|
|
|
|
|
int channels[1];
|
|
|
|
|
channels[0] = 0;
|
|
|
|
|
|
|
|
|
|
int histSize[1];
|
|
|
|
|
histSize[0] = 256;
|
|
|
|
|
|
|
|
|
|
float hranges[2];
|
|
|
|
|
hranges[0] = 0.0f;
|
|
|
|
|
hranges[1] = 255.0f;
|
|
|
|
|
|
|
|
|
|
const float * ranges[1];
|
|
|
|
|
ranges[0] = hranges;
|
|
|
|
|
|
|
|
|
|
cv::MatND hist;
|
|
|
|
|
cv::calcHist(&src, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
|
|
|
|
|
|
|
|
|
|
// ֱ<><D6B1>ͼͳ<CDBC><CDB3>
|
|
|
|
|
cv::Mat ZeroValuesStat(1, 256, CV_8UC1);
|
|
|
|
|
ZeroValuesStat = 0;
|
|
|
|
|
|
|
|
|
|
double srcMaxVal = 0;
|
|
|
|
|
uchar zeroCount = 0;
|
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
|
|
|
{
|
|
|
|
|
ZeroValuesStat.at<uchar>(0, i) = zeroCount;
|
|
|
|
|
|
|
|
|
|
if (hist.at<float>(i) == 0)
|
|
|
|
|
{
|
|
|
|
|
zeroCount++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
srcMaxVal = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (zeroCount == 0)
|
|
|
|
|
{
|
|
|
|
|
src.copyTo(dst);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst.release();
|
|
|
|
|
dst = cv::Mat(src.rows, src.cols, CV_8UC1);
|
|
|
|
|
|
|
|
|
|
// <20><EFBFBD><DEB3><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>Ϊ0<CEAA><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD>ͽ<EFBFBD>ѹ<EFBFBD><D1B9>
|
|
|
|
|
#pragma omp parallel for
|
|
|
|
|
for (int r = 0; r < src.rows; r++)
|
|
|
|
|
{
|
|
|
|
|
for (int c = 0; c < src.cols; c++)
|
|
|
|
|
{
|
|
|
|
|
dst.at<uchar>(r, c) = src.at<uchar>(r, c) - ZeroValuesStat.at<uchar>(0, src.at<uchar>(r, c));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20>ҶȽ<D2B6><C8BD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
double maxVal = 255 - ZeroValuesStat.at<uchar>(0, 255);
|
|
|
|
|
|
|
|
|
|
double ratio = 1.0;
|
|
|
|
|
if (maxVal > 0)
|
|
|
|
|
{
|
|
|
|
|
ratio = 255.0 / maxVal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst.convertTo(dst, CV_8U, ratio, 0);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch(cv::Exception &e)
|
|
|
|
|
{
|
|
|
|
|
e.msg;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch(...)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>Ӧ<EFBFBD><D3A6>ֱֵ<D6B5><D6B1>ͼͶӰ<CDB6><D3B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD>Աȶ<D4B1>
|
|
|
|
|
bool SAHP(cv::Mat &dst,const cv::Mat& src)
|
|
|
|
|
{
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD>֤
|
|
|
|
|
if (src.empty() == true || src.type() != CV_8UC1)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>ֱ<EFBFBD><D6B1>ͼ
|
|
|
|
|
int channels[1];
|
|
|
|
|
channels[0] = 0;
|
|
|
|
|
|
|
|
|
|
int histSize[1];
|
|
|
|
|
histSize[0] = 256;
|
|
|
|
|
|
|
|
|
|
float hranges[2];
|
|
|
|
|
hranges[0] = 0.0f;
|
|
|
|
|
hranges[1] = 255.0f;
|
|
|
|
|
|
|
|
|
|
const float * ranges[1];
|
|
|
|
|
ranges[0] = hranges;
|
|
|
|
|
|
|
|
|
|
// ͳ<><CDB3>ֱ<EFBFBD><D6B1>ͼ
|
|
|
|
|
cv::MatND hist;
|
|
|
|
|
cv::calcHist(&src, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
|
|
|
|
|
|
|
|
|
|
// ֱ<><D6B1>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD>
|
|
|
|
|
cv::Mat ZeroValuesStat(1, 256, CV_8UC1);
|
|
|
|
|
ZeroValuesStat = 0;
|
|
|
|
|
|
|
|
|
|
uchar zeroCount = 0;
|
|
|
|
|
int threshold = (src.rows * src.cols) / (256 * 33); // <20><>ֵ<EFBFBD><D6B5>3%<25><>
|
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
|
|
|
{
|
|
|
|
|
ZeroValuesStat.at<uchar>(0, i) = zeroCount;
|
|
|
|
|
|
|
|
|
|
if (hist.at<float>(i) < threshold)
|
|
|
|
|
{
|
|
|
|
|
zeroCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (zeroCount == 0)
|
|
|
|
|
{
|
|
|
|
|
src.copyTo(dst);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst.release();
|
|
|
|
|
dst = cv::Mat(src.rows, src.cols, CV_8UC1);
|
|
|
|
|
|
|
|
|
|
// <20><EFBFBD><DEB3><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>Ϊ0<CEAA><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD>ͽ<EFBFBD>ѹ<EFBFBD><D1B9>
|
|
|
|
|
for (int r = 0; r < src.rows; r++)
|
|
|
|
|
{
|
|
|
|
|
for (int c = 0; c < src.cols; c++)
|
|
|
|
|
{
|
|
|
|
|
dst.at<uchar>(r, c) = src.at<uchar>(r, c) - ZeroValuesStat.at<uchar>(0, src.at<uchar>(r, c));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20>ҶȽ<D2B6><C8BD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
double maxVal = 255 - ZeroValuesStat.at<uchar>(0, 255);
|
|
|
|
|
|
|
|
|
|
double ratio = 1.0;
|
|
|
|
|
if (maxVal > 0)
|
|
|
|
|
{
|
|
|
|
|
ratio = 255.0 / maxVal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst.convertTo(dst, CV_8UC1, ratio);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20><>˹<EFBFBD><CBB9>Լ<EFBFBD><D4BC>ֱ<EFBFBD><D6B1>ͼͶӰ<CDB6>㷨<EFBFBD><E3B7A8> <20><><EFBFBD><EFBFBD>һ<EFBFBD>㡢<EFBFBD>ٶȿ<D9B6>
|
|
|
|
|
bool GNRHP(cv::Mat& dst, const cv::Mat &src, bool bDeNoise)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD>֤
|
|
|
|
|
if (src.empty() == true || src.type() != CV_8UC1)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>
|
|
|
|
|
cv::Mat srcCopy;
|
|
|
|
|
src.copyTo(srcCopy);
|
|
|
|
|
|
|
|
|
|
// <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ɢ<EFBFBD><C9A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˲<EFBFBD>
|
|
|
|
|
if (bDeNoise == true)
|
|
|
|
|
{
|
|
|
|
|
cv::medianBlur(srcCopy, srcCopy, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20>ڶ<EFBFBD><DAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><CBB9>Լ<EFBFBD><D4BC>ϸ<EFBFBD><CFB8><EFBFBD><EFBFBD>ǿ
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> T
|
|
|
|
|
double minVal = 0, maxVal = 0;
|
|
|
|
|
cv::minMaxLoc(srcCopy, &minVal, &maxVal);
|
|
|
|
|
|
|
|
|
|
double T = 2 * (maxVal - minVal);
|
|
|
|
|
|
|
|
|
|
if (T == 0)
|
|
|
|
|
{
|
|
|
|
|
src.copyTo(dst);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><>ͼ
|
|
|
|
|
cv::Mat BaseImg;
|
|
|
|
|
srcCopy.convertTo(BaseImg, CV_32FC1);
|
|
|
|
|
|
|
|
|
|
// <20><>˹ģ<CBB9><C4A3> <20><>ȡ<EFBFBD><C8A1>ͼ 3 * 1.667 = 5
|
|
|
|
|
cv::GaussianBlur(BaseImg, BaseImg, cv::Size(5,5), 1.667);
|
|
|
|
|
|
|
|
|
|
// ϸ<><CFB8>ͼ
|
|
|
|
|
cv::Mat DetailImg;
|
|
|
|
|
cv::Mat srcF;
|
|
|
|
|
srcCopy.convertTo(srcF, CV_32FC1);
|
|
|
|
|
DetailImg = srcF - BaseImg;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><CFB5> <20><><EFBFBD><EFBFBD>Ӧ
|
|
|
|
|
cv::minMaxLoc(BaseImg, &minVal, &maxVal);
|
|
|
|
|
double BaseImgSpan = maxVal - minVal;
|
|
|
|
|
cv::minMaxLoc(DetailImg, &minVal, &maxVal);
|
|
|
|
|
double DetailImgSpan = maxVal - minVal;
|
|
|
|
|
double ratio = BaseImgSpan / DetailImgSpan * 2.0;
|
|
|
|
|
|
|
|
|
|
// ͼ<><CDBC><EFBFBD>ϳ<EFBFBD> <20>ɸĽ<C9B8>
|
|
|
|
|
cv::Mat dstimg;
|
|
|
|
|
cv::addWeighted(BaseImg, 1.0, DetailImg, ratio, 0, dstimg);
|
|
|
|
|
|
|
|
|
|
cv::minMaxLoc(dstimg, &minVal, &maxVal);
|
|
|
|
|
dstimg = dstimg - minVal;
|
|
|
|
|
maxVal -= minVal;
|
|
|
|
|
dstimg.convertTo(dstimg, CV_8U, 255.0 / maxVal, 0);
|
|
|
|
|
|
|
|
|
|
// ϸ<><CFB8><EFBFBD><EFBFBD>ǿͼ<C7BF><CDBC>HP(ֱ<><D6B1>ͼͶӰ<CDB6><D3B0>
|
|
|
|
|
return HP(dst, dstimg);
|
|
|
|
|
}
|
|
|
|
|
catch(cv::Exception &e)
|
|
|
|
|
{
|
|
|
|
|
e.msg;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch(...)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20><>˹Լ<CBB9><D4BC>ֱ<EFBFBD><D6B1>ͼͶӰ<CDB6>㷨<EFBFBD><E3B7A8> <20><><EFBFBD><EFBFBD><EFBFBD>á<EFBFBD><C3A1>ٶ<EFBFBD><D9B6><EFBFBD>
|
|
|
|
|
bool GRHP(cv::Mat& dst, const cv::Mat &src, bool bDeNoise)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD>֤
|
|
|
|
|
if (src.empty() == true || src.type() != 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD>
|
|
|
|
|
cv::Mat srcCopy;
|
|
|
|
|
src.copyTo(srcCopy);
|
|
|
|
|
|
|
|
|
|
// <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ɢ<EFBFBD><C9A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˲<EFBFBD>
|
|
|
|
|
if (bDeNoise == true)
|
|
|
|
|
{
|
|
|
|
|
cv::medianBlur(srcCopy, srcCopy, 3); // <20><><EFBFBD><EFBFBD>ֵ<EFBFBD>˲<EFBFBD><CBB2><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20><>˹<EFBFBD>˲<EFBFBD><CBB2>˳ߴ<CBB3>
|
|
|
|
|
int kernelSize = 7;
|
|
|
|
|
double delta = 2.0;
|
|
|
|
|
|
|
|
|
|
// <20><>Ե<EFBFBD><D4B5><EFBFBD><EFBFBD>
|
|
|
|
|
cv::copyMakeBorder(srcCopy, srcCopy, kernelSize, kernelSize, kernelSize, kernelSize, BORDER_REPLICATE);
|
|
|
|
|
|
|
|
|
|
// <20>ڶ<EFBFBD><DAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹Լ<CBB9><D4BC>ϸ<EFBFBD><CFB8><EFBFBD><EFBFBD>ǿ
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> T
|
|
|
|
|
double minVal = 0, maxVal = 0;
|
|
|
|
|
cv::minMaxLoc(srcCopy, &minVal, &maxVal);
|
|
|
|
|
|
|
|
|
|
double T = 2 * (maxVal - minVal);
|
|
|
|
|
|
|
|
|
|
if (T == 0)
|
|
|
|
|
{
|
|
|
|
|
src.copyTo(dst);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// <20><>ͼ
|
|
|
|
|
cv::Mat BaseImg;
|
|
|
|
|
srcCopy.convertTo(BaseImg, CV_32FC1);
|
|
|
|
|
|
|
|
|
|
#pragma omp parallel for
|
|
|
|
|
for (int r = kernelSize; r < srcCopy.rows - kernelSize; r++)
|
|
|
|
|
{
|
|
|
|
|
for (int c = kernelSize; c < srcCopy.cols - kernelSize; c++)
|
|
|
|
|
{
|
|
|
|
|
double val = 0;
|
|
|
|
|
double sumS = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = -kernelSize; i < kernelSize + 1; i++)
|
|
|
|
|
{
|
|
|
|
|
for (int j = -kernelSize; j < kernelSize + 1; j++)
|
|
|
|
|
{
|
|
|
|
|
// <20><>˹<EFBFBD>˲<EFBFBD><CBB2><EFBFBD>ϵ<EFBFBD><CFB5><EFBFBD><EFBFBD>GC
|
|
|
|
|
double GC = exp(-(i * i + j * j)/ (2 * delta * delta));
|
|
|
|
|
|
|
|
|
|
// <20>ҶȲ<D2B6>ֵԼ<D6B5><D4BC>ϵ<EFBFBD><CFB5><EFBFBD><EFBFBD>DR
|
|
|
|
|
double diff = srcCopy.at<uchar>(r + i, c + j) - srcCopy.at<uchar>(r, c);
|
|
|
|
|
double DR = T / (pow(diff, 2) + T);
|
|
|
|
|
|
|
|
|
|
// <20>ϳ<EFBFBD>ϵ<EFBFBD><CFB5>
|
|
|
|
|
double CC = GC * DR;
|
|
|
|
|
|
|
|
|
|
// <20>ۺ<EFBFBD>
|
|
|
|
|
val += CC * srcCopy.at<uchar>(r + i, c + j);
|
|
|
|
|
sumS += CC;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val = val / sumS;
|
|
|
|
|
BaseImg.at<float>(r, c) = float(val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ϸ<><CFB8>ͼ
|
|
|
|
|
cv::Mat DetailImg;
|
|
|
|
|
cv::Mat srcF;
|
|
|
|
|
srcCopy.convertTo(srcF, CV_32FC1);
|
|
|
|
|
DetailImg = srcF - BaseImg;
|
|
|
|
|
|
|
|
|
|
// BaseImg DetailImg <20>ñ<EFBFBD>
|
|
|
|
|
BaseImg = BaseImg(cv::Range(kernelSize, BaseImg.rows - kernelSize), cv::Range(kernelSize, BaseImg.cols - kernelSize));
|
|
|
|
|
DetailImg = DetailImg(cv::Range(kernelSize, DetailImg.rows - kernelSize), cv::Range(kernelSize, DetailImg.cols - kernelSize));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><CFB5> <20><><EFBFBD><EFBFBD>Ӧ
|
|
|
|
|
cv::minMaxLoc(BaseImg, &minVal, &maxVal);
|
|
|
|
|
double BaseImgSpan = maxVal - minVal;
|
|
|
|
|
cv::minMaxLoc(DetailImg, &minVal, &maxVal);
|
|
|
|
|
double DetailImgSpan = maxVal - minVal;
|
|
|
|
|
double ratio = BaseImgSpan / DetailImgSpan * 2.0;
|
|
|
|
|
|
|
|
|
|
// ͼ<><CDBC><EFBFBD>ϳ<EFBFBD> <20>ɸĽ<C9B8>
|
|
|
|
|
cv::Mat dstimg;
|
|
|
|
|
cv::addWeighted(BaseImg, 1.0, DetailImg, ratio, 0, dstimg);
|
|
|
|
|
|
|
|
|
|
cv::minMaxLoc(dstimg, &minVal, &maxVal);
|
|
|
|
|
dstimg = dstimg - minVal;
|
|
|
|
|
maxVal -= minVal;
|
|
|
|
|
dstimg.convertTo(dstimg, CV_8U, 255.0 / maxVal, 0);
|
|
|
|
|
|
|
|
|
|
// ϸ<><CFB8><EFBFBD><EFBFBD>ǿͼ<C7BF><CDBC>HP(ֱ<><D6B1>ͼͶӰ<CDB6><D3B0>
|
|
|
|
|
return HP(dst, dstimg);
|
|
|
|
|
}
|
|
|
|
|
catch(cv::Exception &e)
|
|
|
|
|
{
|
|
|
|
|
e.msg;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch(...)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|