仿照“全能扫描王”的图像增强-由原理到实现

发布时间 2023-03-22 19:06:53作者: jsxyhelu
一、算法目标:
实现这种背景去除增强的效果,特别是在“全能扫描王”中该算法得到了典型的应用。

 

 

二、使用PS进行模拟
图像处理算法很多时候就是对成熟经验的模拟和复现。首先我们来看PS下的处理。
 
1.手机拍一张需要电子版的纸质文件:

 

 

 
2.打开PS,复制背景,得到图层1:
 

 

 

3.对图层1使用滤镜:高斯模糊,半径100像素:
 

 

 

4.改变图层1混合模式:划分:

 

 

 
5.添加曲线调整层,压暗文字:
 

 

 

6.合并可见图层。
 
7.柔角画笔擦掉彩色噪点和边缘露出的拍摄背景:
 
那么,这个效果的确实很好的,关键问题就是“划分”是什么操作?
PS中划分模式的计算公式:
结果色 = (基色 / 混合色) * 255
分析每个颜色通道的数值,并用基色分割混合色基色数值大于或等于混合色数值,混合出的颜色为白色。基色数值小于混合色,结果色比基色更暗。因此结果色对比非常强
两个一样的图层(复制基色图层),图层混合模式改为划分结果色就是白色
混合色为白色,结果色就是基色;混合色为黑色,结果色就是白色;
公式验证:r值(255/79)*255=823;结果色大于255,系统就默认为最大值255;以公式推断基色数值大于或等于混合色数值,混合出的颜色为白色。

 

 

 

 

 

 


 
三、整理流程,转换为代码
 
图像A,B,为同一场景在不一样的光照拍摄图片,那么:
光照分布 L = A / B
如果已知 A, L ,则 B = A / L (B 为A去光照的结果)
这里L约等于 gaussFilter(A, 大核)

 

 
再进一步转换为代码:
 
    Mat src = imread("t1.jpeg");
    src.convertTo(src,CV_32FC3,1.0/255);
    Mat gauss;
    Mat dst = src.clone();
    cv::GaussianBlur(src,gauss,Size(101,101),0);
    
    //划分
    //如果混合色与基色相同则结果色为白色
    //如混合色为白色则结果色为基色不变
    //如混合色为黑色则结果色为白色
    dst = src/gauss;
    waitKey();
 

 

 

在这个基础上,进一步进行封装:
 
//USM图像增
void ImageSharp(Mat &src,Mat &dst,int nAmount = 1000)
{
    double sigma = 3;  
    int threshold = 1;  
    float amount = nAmount/100.0f;  
    Mat imgBlurred;
    GaussianBlur(src, imgBlurred, Size(), sigma, sigma);
    Mat lowContrastMask = abs(src-imgBlurred)<threshold;
    dst = src*(1+amount)+imgBlurred*(-amount);
    src.copyTo(dst, lowContrastMask);
}

int main()
{
    Mat src = imread("E:\\未来项目\\白板增强\\images\\t1.jpeg");
    src.convertTo(src,CV_32FC3,1.0/255);
    Mat gauss;
    Mat dst1 = src.clone();
    Mat dst2 = src.clone();
    cv::GaussianBlur(src,gauss,Size(101,101),0);
    dst1 = src/gauss;
    blur(src,gauss,Size(101,101));
    dst2 = src/gauss;
    //划分
    ImageSharp(dst2,dst2,101);
    waitKey();
    return 0;
}
效果更好

 

 

 
四、原理解析
算法的效果不错,下一步就是要分析为什么了。
关键点在于blur的话就可以思考得更清楚了,在101*101的格子里面算平均值,然后前景和其进行比较,对于那些有字的,肯定是小于平均值,关键是对于背景,肯定大于平均值,这样一除就变成了全白,问题也就清楚了。
我也找到了ImageShop作者对这个算法的分析,他是专门做增强的,分析的比较有道理:

“这个算法在此类图像中能够成功的核心是:在原图中比较黑的文字部分,占用的整体是比较少的,当大半径模糊时,模糊的值是接近纸张之类的颜色的,也就是比较靠近白色,所以结果基本上没什么变化,而纸张那些地方的颜色,因为模糊的值和他们的原始值基本差不多,所以Src/Blur基本接近1,在乘以255,所以结果就变为白色了。” 

五、补充材料
1、发现OpenCV自带Divide算法,未进一步检测,我自信目前的解决是不错的。
divide
Performs per-element division of two arrays or a scalar by an array.
C++: void divide(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
C++: void divide(double scale, InputArray src2, OutputArray dst, int dtype=-1)
Python: cv2.divide(src1, src2[, dst[, scale[, dtype]]]) → dst
Python: cv2.divide(scale, src2[, dst[, dtype]]) → dst
C: void cvDiv(const CvArr* src1, const CvArr* src2, CvArr* dst, double scale=1)
Python: cv.Div(src1, src2, dst, scale=1) → None
Parameters:   
src1 – first input array.
src2 – second input array of the same size and type as src1.
scale – scalar factor.
dst – output array of the same size and type as src2.
dtype – optional depth of the output array; if -1, dst will have depth src2.depth(), but in case of an array-by-array division, you can only pass -1 when src1.depth()==src2.depth().
The functions divide divide one array by another:
or a scalar by an array when there is no src1 :
When src2(I) is zero, dst(I) will also be zero. Different channels of multi-channel arrays are processed independently.