
丙烯馬克筆在素描紙上手寫字質感真的非常棒。那么我們在素描紙上寫的字如何轉化為一種干凈的電子圖呢現在我們僅僅對算法的過程進行展示暫時不考慮性能問題。手寫算法的詳細處理過程如下先識別字體邊緣輪廓根據極大的黑白亮度差異來進行定位。我們在進行參數的閾值選擇的時候我傾向于的原則是使用不敏感數值通過使用更多的維度或某種正確的方式來絕對的降低對單一參數的敏感性依賴。比如這里亮度的差異值 crdif, 其實在 5 ~100之間都能夠勝任因此我們可使用的數值基本上可以隨意for (int r margin; r lb.Height - margin; r) { for (int c margin; c lb.Width - margin; c) { var cr lb.GetPixel(c, r).Avg(); var crrd ps_rd_5.Select(a lb.GetPixel(a.X c, a.Y r).Avg()).ToList(); var crmin crrd.Min(); var crmax crrd.Max(); var crdif crmax - crmin; var ok crdif 100; if (!ok) { psrednot[c, r] true; continue; } psred[c, r] true; } }標記后的區域如下可以看到非常好的把邊緣區域都標記了出來由這樣閉合的字體輪廓我們可以得到界限清晰的3部分無效區域、邊緣區域、和文字內部區域var bks ImgHelper.ColorBlockLock2_8_2DArray(psrednot.Clone() as bool[,]);由此我們可以獲取到除了字體外邊緣之外的模塊的劃分一部分是白色背景區域一部分是字體的黑色內部區域然后根據區域的邊緣點大概率亮度值可以非常明顯的獲取到字體的內部區域。這里用到的亮度 80也是一個極其安全的數值它基本在50~128之間都可以正常工作。這里最好的方式是根據圖片的直方圖來動態的檢測兩種主亮度這里我們為了簡單起見隨意使用一個絕對安全的固定數值 80 另外0.6的概率參數也是一個極其安全的數值因為在理論情況下目標區域的外周由于已經處于了文字內部所以基本都是暗色的邊緣它的概率基本上是接近 1反之的白色背景區域的概率基本上都接近于 0所以這里的參數 0.6 也是極其安全DictionaryListPoint, ListPoint bks_edge_full_dict new DictionaryListPoint, ListPoint(); foreach (var bk in bks) { var ps_edge bk.Where(a a.RoundPsN(1).Any(b !psrednot[b.X, b.Y])).ToList(); //是否屬于字體內部輪廓 var ps_edge_ok ps_edge.Count(a lb.GetPixel(a.X, a.Y).Avg() 80) ps_edge.Count * 0.6; if (!ps_edge_ok) continue; bks_edge_full_dict[ps_edge] bk; }這樣我們就得到了文字及其近邊緣區域的所有的點然后將他們合并標記為綠色顯示//合并最初的邊緣區域 var ps_black_all psred.Clone() as bool[,]; foreach (var bk in bks_edge_full_dict) { foreach (var p in bk.Value) { ps_black_all[p.X, p.Y] true; } }就可以剔除主要的白色背景的無效區域 接下來我們要對字體的邊緣部分進行透明過渡的處理如果我們單純的進行二值分割那么邊緣會在不同的亮度閾值下呈現出明顯的鋸齒無論這個參數設置為多少都會有明顯的鋸齒。所以最理想的就是進行一種無級的轉換根據邊緣過渡區域的亮度的上限和下限與透明度進行關聯映射。越接近字體亮度的越接近透明 255 越接近白色背景區域亮度的越接近透明度 0并且為了避免對字體的暗色區域進行錯誤的透明轉化或干擾我們可以僅針對于高于兩者亮度均值的部分的像素點來進行轉化//安全區的亮度均值,透明度 255 var light_avg_font bks_edge_full_dict.Values. SelectMany(a a). Average(a lbInit.GetPixel(a.X, a.Y).Avg()); //移除區的亮度均值,透明度 0 var light_avg_remove remove_area_lightall.Average(); var light_min light_avg_remove; var light_max light_avg_font * 1.0; var light_avg (light_min light_max) / 2; var light_range light_max - light_min 1; //對邊緣區域進行透明度設置 for (int r 0; r lb.Height; r) { for (int c 0; c lb.Width; c) { if (psred[c, r]) { if (lbInit.GetPixel(c, r).Avg() light_avg) { var v (int)((lbInit.GetPixel(c, r).Avg() - light_min) / (int)light_range * 255); v Math.Max(0, Math.Min(255, v)); lb.SetPixel(c, r, Color.FromArgb(v, lbInit.GetPixel(c, r))); } else { lb.SetPixel(c, r, Color.FromArgb(255, lbInit.GetPixel(c, r))); } } } }處理后的效果來看一下它在不同背景色下的摳像效果整體看起來還不錯尤其是邊緣區域細節也都有保留但是唯一的缺點是字體中心仍然有一些偏白的區域它反映的是白色素描紙的底色這里當我們更換為彩色或某種指定顏色背景的時候其實應該要稍微的處理一下讓它保持與背景的色調的一致性所以可以整體上讓它在透明度上稍微加一些透明這樣可以與后背景稍微融合就不會顯得過于突兀。這一點是可以進一步去優化的。