仿照「全能掃描王」的影象增強-由原理到實現

2022-12-20 21:01:24
一、演演算法目標:
實現這種背景去除增強的效果,特別是在「全能掃描王」中該演演算法得到了典型的應用。

 

 

二、使用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.