C# 图像边缘融合 - 简单边缘场景 (三)

很喜欢看图像算法在处理过程中的状态变化,有一种蜕变的神奇感觉,就好像代码自己有了思想,在跳动着和我讲话,像是一个一个的小精灵,在组织着自己的思想,然后到达我要去到的终点。


背景

上一篇文章 C# 图像边缘融合 - 简单边缘场景 (二)-CSDN博客 实现了一种简单图像的边缘融合,用于对图像内容在与相近色的图像边框融合的时候,进行了线性的过渡处理,

但是它有2个缺点:

1,当拍照的环境背景不具有线性特征的时候,会破坏背景;

2,对于主体内容的边缘过于复杂的情况,过渡效果不好

这篇文章主要在以上两个方面进行了优化,对不完全纯色的背景图像,当它与背景融合的时候,

改进思路有2点:

1,通过透明度过渡的方式来进行融合,不损失图像内容,

2,外形轮廓的提取时,关键特征点的精细路径转变为,使用指定步长的规则轮廓来进行贴近拟合,最后进行一次均值平滑处理,以获得较好的外包围轮廓。


目标

处理一种简单背景的图像,覆盖相框的融合处理。

条件:基于两张相同尺寸的图像。

要求:边框不受到任何的破坏

要求:图像主内容不受到任何的破坏

要求:图像主内容能够容忍异形复杂边缘

要求:内容与边框的过渡,不能破坏过渡区的内容

相框图:  内容图:

如下图所示,将相框图与内容图进行结合产生了第3张融合图,这里的边缘需要被进行融合处理:


处理过程

1,找出相框图片的内容区域,即非透明区域,标记为红色区域。

  for (int r = margin; r < lb.Height - margin; r++)
  {
      for (int c = margin; c < lb.Width - margin; c++)
      {
          var cr = lb.GetPixel(c, r);
          var strans = cr.A == 0;
          if (strans)
          {
              arr_ps_trans[c, r] = true;
          }
      }
  }

2,标记明显关键点,即绿色区域。

var split_count = 4;
bool[,] rd_channel_dif_max_pt = new bool[lb.Width, lb.Height];
var psrd5 = GetCirclePoints(new Point(), (int)Math.Round(round_len / (double)split_count * (split_count + 1)));
var lst_rd_channel_dif_max_pt = new Point[lb.Width * lb.Height];
var lst_rd_channel_dif_max_pt_idx = 0;
for (int r = margin; r < lb.Height - margin; r++)
{
    for (int c = margin; c < lb.Width - margin; c++)
    {
        var ls1 = psrd5.Select(a => new Point(a.X + c, a.Y + r)).Where(a => rtvalid.Contains(a)).ToList(); 
        var dmax = ls1.Max(a => rd_channel_dif_max[a.X, a.Y]); 
        //太小的差异,丢弃,影响细节保留度
        if (dmax < chanelDifFilter) continue; 
        var ptmax = ls1.First(a => rd_channel_dif_max[a.X, a.Y] == dmax); 
        //仅仅保留有效范围内的
        if (!rt_border.Contains(ptmax)) continue;  
        rd_channel_dif_max_pt[ptmax.X, ptmax.Y] = true;
        lst_rd_channel_dif_max_pt[lst_rd_channel_dif_max_pt_idx] = ptmax;
        lst_rd_channel_dif_max_pt_idx++;
    }
}

3,基于有效特征点,从矩形向内贴近拟合,并遵循要求:

        => 轮廓步长不宜过长,避免贴合度过低而丢失太多细节;

        => 向内贴合的线段外角度不能过于锐化,尽量使贴合平整;

        => 对最终的逼近结果轮廓进行一次均值平滑处理,提升融合边界的视觉效果;

while (true)
{
    var find_valid = false;
    var order_lst = point_angle_order_dict.ToList();
    var points_rotate_360_in_rectborder_process = point_angle_order_dict.ToList();
    var step_to_inner = step / 3;
    for (int i = 0; i < points_rotate_360_in_rectborder_process.Count; i++)
    {
        var pInfo = points_rotate_360_in_rectborder_process[i];
        var p = pInfo.pt;
        //从边缘点p,到达中心点之间的线段上的所有的点,按照距离边缘点远近排序
        var ls2 = GetLinePoints(p, pt_center).OrderBy(a => ImgHelper.ptDist2(a, p)).ToList();
        //分组,减少计算量
        ls2 = ls2.GroupBy(a => new { x = a.X / step_to_inner, y = a.Y / step_to_inner }).Select(a => a.First()).ToList();
        //依次进行靠近模拟,遇到非法的点则终止
        var p2_rst = p;
        foreach (var p2 in ls2)
        {
            var lstmpp = point_angle_order_dict;
            var lstmp = new List<PtAgInfo>();
            //向内的角度,禁止过于尖锐,如果低于指定度数,则跳过 
            var iIdx = pInfo.idx;
            var p1 = iIdx == 0 ? lstmpp[lstmpp.Count - 1] : lstmpp[iIdx - 1];
            var p3 = iIdx == lstmpp.Count - 1 ? lstmpp[0] : lstmpp[iIdx + 1];
            var ag2 = CalculateClockwiseAngle(p1.pt, p2, p3.pt);
            if (ag2 < inner_dgr_limit) break;

            lstmp = point_angle_order_dict.Select(a => new PtAgInfo()
            {
                angle = a.angle,
                pt = a.pt,
                idx = a.idx
            }).ToList();
            lstmp[pInfo.idx].pt = p2;
            //检测包裹性
            GraphicsPath gp = new GraphicsPath();
            gp.AddPolygon(lstmp.Select(a => a.pt).ToArray());
            var ok = ps_content_key.All(a => gp.IsVisible(a));
            if (!ok) break;

            if (p2 != p2_rst)
            {
                p2_rst = p2;
                find_valid = true;
                break;
            }
        }
        pInfo.pt = p2_rst;
    }
    //没有可继续收缩的,则退出
    if (!find_valid) break;
}
//使闭合
point_angle_order_dict.Add(point_angle_order_dict.First());

4,对非内容区,底层默认以相框边缘色为背景色填充,上层使用人像内容,按照距离边缘点的远近不同,分别设置不同的透明度,显示不同程度的底背景,达到融合的视觉效果:

     

//稍微扩展
ps_in_line = ps_in_line.SelectMany(a => a.RoundPs5()).ToList();
//计算扩展后的点与内容区的点的交集,作为实际的目标连接点
var lst_inter_tmp = ps_in_line.Where(a => arr_ps_content_withoutext_border[a.X, a.Y]).ToList();
if (lst_inter_tmp.Count == 0) continue;
var tmp_avg_x = (int)Math.Round(lst_inter_tmp.Average(a => a.X));
var tmp_avg_y = (int)Math.Round(lst_inter_tmp.Average(a => a.Y));
var pt_target_to_connect_in_contentborder = new System.Drawing.Point(tmp_avg_x, tmp_avg_y);
 
//取值自动色 
var ps = GetPointsOnLine(pt, pt_target_to_connect_in_contentborder);
if (ps.Count <= 5) continue;
ps = ps.SelectMany(a => a.RoundPs9()).ToList();

//过度色:中间点到目标点
var dist_all = ImgHelper.ptDist(pt, pt_target_to_connect_in_contentborder);
foreach (var p in ps)
{
    //根据距离目标点的远近来设置透明度
    var dist_tmp = ImgHelper.ptDist(p, pt_target_to_connect_in_contentborder);
    var opt = 255 - Math.Min(255, (int)Math.Round((dist_tmp / (double)dist_all) * 255));
    var crtmp = lb2.GetPixel(p.X, p.Y);

    //背景色混合前景色
    var cr_rst = BlendColors(cr_base_common_content_bg, Color.FromArgb(opt, crtmp.R, crtmp.G, crtmp.B)); 
    lbtmp.SetPixel(p.X, p.Y, cr_rst);
    imgtmp_tmp.SetPixel(p.X, p.Y, cr_rst); 
}

其他示例(背景与边框亮度差异较大,看起来差强人意,勉强可以接受):


局限性

对于不同拍摄场景,需要适当调整参数,比如人像全景,或者人像半身照,对边缘的过渡要求不同,可能需要4向融合,可能需要两侧融合,需要根据实际调整; 并且,本文主要演示了实现融合的一种概念化思路,在实际落地应用时,仍然要进行大量的兼容性测试,和性能优化。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值