在图像算法的高层次处理中,有一类很典型的应用,就是图像修复算法。图像在采集、传输、预处理过程中,都可能会发生图像数据被修改、损失和缺失等问题(例如:部分图像内容被污染、雾霾等),另外,在实际室外拍照的时候,可能会将用户不想需要的对象(例如:路人甲等)也摄入照片影像中,需要将这多余图像内容去除。这一类的图像处理算法可以统称为 图像修复,对污染后的图像进行去噪、修补、变换等操作还原原始图像内容。在图像修复中最主要的一类处理就是 图像修补,就是将图像中一小块被污染的区域通过各种算法尽量还原成原始图像。
如果图像中需要修复的区域都比较小,一个常见的算法就是将需要修复区域的邻边像素进行扩散,填补需要修复的区域,从而达到图像修补的目的。
一、图像修补基本概念
在一幅需要修复的图像中,我们将整幅图像用F表示,需要修复的区域块称之为靶区域,用符号D来表示,保留原始图像信息的区域(F-D)称之为 源区域。
例如:在如下图像中,有一块被红色污损的区域,这里我们将红色区域称之为 靶区域,除了红色区域以外的图像区域,称之为 源区域。而我们的传统图像修复算法,就是利用 靶区域 附近的 源像素数据,经过变换拟合来逐渐填充 靶区域的像素,从而达到图像修补的效果。
二、全变分模型原理
关于全变分模型的公式推断比较复杂,主要依据 欧拉-拉格朗日 方程,这里不再详述,直接提供手工推到过程。
三、算法步骤
全变分模型主要采用迭代的方式,逐步填充需要修补的区域,直至将所有靶区域像素全部填充完成。
令 是原始图像数据, 是每次迭代的图像数据
1、初始时
2、接下来每次迭代时:
这里 、、 三个是迭代的参数
3、根据上面公式循环迭代操作 ,可以设置迭代的次数,最后一次的 是最终结果图像数据。
四、代码实现
这里提供一个简单的全变分图像修补算法
int32_t XImage::DenoizeTotalVariation(
int32_t iterate_count,
double_t dt,
double_t epsilon,
double_t lambda,
XImage& out_img )
{
int32_t x, y, k;
int32_t ret = 0;
if (!image_valid_)
{
return XERR_BAD_STATE;
}
// 分配矩阵数据
int32_t pixel_count = width_ * height_;
std::unique_ptr<double_t[]> mtx_org_ptr = std::make_unique<double_t[]>(pixel_count);
std::unique_ptr<double_t[]> mtx_src_ptr = std::make_unique<double_t[]>(pixel_count);
std::unique_ptr<double_t[]> mtx_dst_ptr = std::make_unique<double_t[]>(pixel_count);
double_t* mtx_org = mtx_org_ptr.get( );
double_t* mtx_src = mtx_src_ptr.get( );
double_t* mtx_dst = mtx_dst_ptr.get( );
// 将原始图像数据转换成灰度图,再归一化到二维矩阵中
uint8_t* line_data = image_data_;
k = 0;
for (y = 0; y < height_; y++)
{
uint8_t* rgb_data = line_data;
for (x = 0; x < width_; x++)
{
uint32_t R = rgb_data[0] * 77;
uint32_t G = rgb_data[1] * 151;
uint32_t B = rgb_data[2] * 28;
uint32_t gray = (R + G + B) >> 8;
mtx_org[k] = (double_t)(gray / 255.0f);
mtx_src[k] = mtx_org[k];
mtx_dst[k] = mtx_org[k];
k++;
rgb_data += pixel_bytes_;
}
line_data += line_bytes_;
}
int32_t x_end = (width_ - 1);
int32_t y_end = (height_ - 1);
double_t epsilon_pwr2 = epsilon * epsilon;
double_t *org_data, *src_data, *dst_data;
for (k = 0; k < iterate_count; k++)
{
org_data = mtx_org;
src_data = mtx_src;
dst_data = mtx_dst;
// 进行一次迭代操作
for (y = 0; y < height_; y++)
{
for (x = 0; x < width_; x++)
{
double_t dbl_center_data = src_data[0] * 2.0f;
double_t fourfold_center_data = src_data[0] * 4.0f;
int32_t up = y - 1;
int32_t down = y + 1;
int32_t left = x - 1;
int32_t right = x + 1;
if (up < 0) up = 0;
if (down >= height_) down = y;
if (left < 0) left = 0;
if (right >= width_) right = x;
double_t* left_data = src_data - 1;
double_t* right_data = src_data + 1;
double_t* up_data = src_data - width_;
double_t* dn_data = src_data + width_;
if (x == 0) left_data = src_data;
if (x == x_end) right_data = src_data;
if (y == 0) up_data = src_data;
if (y == y_end) dn_data = src_data;
// 对x求一阶偏导
double_t pd_x = (right_data[0] - left_data[0]) / 2;
// 对y求一阶偏导
double_t pd_y = (dn_data[0] - up_data[0]) / 2;
// 对x求二阶偏导
double_t pd_xx = (right_data[0] + left_data[0]) - dbl_center_data;
// 对y求二阶偏导
double_t pd_yy = (up_data[0] + dn_data[0]) - dbl_center_data;
// 先对x再对y求二阶偏导
double_t pd_xy = (right_data[0] + left_data[0] + up_data[0] + dn_data[0]) - fourfold_center_data;
double_t pd_x_pwr2 = pd_x * pd_x;
double_t pd_y_pwr2 = pd_y * pd_y;
double_t temp_num = pd_yy * (pd_x_pwr2 + epsilon_pwr2) + pd_xx * (pd_y_pwr2 + epsilon_pwr2) - (2 * pd_x * pd_y * pd_xy);
double_t temp_den = (pd_x_pwr2 + pd_y_pwr2 + epsilon_pwr2);
//temp_den = pow(temp_den, 1.5);
temp_den = temp_den * temp_den * temp_den;
temp_den = sqrt(temp_den);
dst_data[0] += dt * (temp_num / temp_den + lambda*(org_data[0] - src_data[0]));
org_data++;
src_data++;
dst_data++;
}
}
// 当次迭代结果数据 作为下一次迭代的源数据
memcpy(mtx_src, mtx_dst, pixel_count*sizeof(double_t));
} // 迭代次数完成
//
// 将结果矩阵数据转换到图像数据中
//
if (!IsSameParameter(out_img))
{
out_img.Release( );
ret = out_img.Allocate(PXL_FORMAT_8BIT_GRAY, width_, height_);
XASSERT(ret == XOK);
}
uint8_t* gray_line = out_img.image_data_;
src_data = mtx_src;
for (y = 0; y < height_; y++)
{
uint8_t* gray_data = gray_line;
for (x = 0; x < width_; x++)
{
int32_t gray_val = static_cast<int32_t>(src_data[0]*255);
if (gray_val < 0) gray_val = 0;
if (gray_val > 255) gray_val = 255;
(*gray_data) = static_cast<uint8_t>(gray_val);
gray_data += out_img.pixel_bytes_;
src_data++;
}
gray_line += out_img.line_bytes_;
}
return XOK;
}
void Test()
{
XImage org_img, atdenoise_img;
org_img.LoadFromBmp("d:/input.bmp");
int32_t iterate_count = 40;
double_t epsilon = 0.1f;
double_t dt = epsilon / 5.0f;
double_t lambda = 0.01f;
ret = org_img.DenoizeTotalVariation(iterate_count, dt, epsilon, lambda, atdenoise_img);
atdenoise_img.SaveToBmp("d:/denoised.bmp");
}
总结
本章节主要描述全变分模型的图像去噪和修复效果,在正常应用中,如果对整个图像数据进行全变分迭代,则可以达到图像去噪的效果;如果仅仅对污损区域进行全变分迭代处理,则可以达到污损区域修复效果。不过在实际应用中,全变分模型仅仅适合非常细小区域的修补,对于大块污损区域的修补效果并不好,需要采用后续讲解到的其他的方法。