OpenCV数字图像处理实战一:去水印(C++)-程序员资料

技术标签: c++  计算机视觉  opencv  

OpenCV数字图像处理实战一:去水印(C++)

1、简单版去水印

1.1 获取原图

 //  1. 获取原图
    Mat src = imread("E:\\img\\3.jpg");
	if (src.empty())
	{
		cout << "No Image!" << endl;
		system("pause");
		return -1;
	}
    imshow("原图", src);

image-20221011151939877

1.2 灰度化

   //   2. 灰度化
        Mat gray;
        cvtColor(src, gray, COLOR_BGR2GRAY);
        imshow("灰度图", gray);

image-20221011151921669

1.3 二值化

//  3. 图像二值化,筛选出白色区域部分
        Mat binary;
        //  在这里使用图像的平均值作为阈值T
        Scalar T = mean(gray);
        threshold(gray, binary, 220, 255, THRESH_BINARY);
        imshow("二值化图", binary);

image-20221011151907703

1.4 生成掩膜图

最关键在这一步,这部分的掩膜图像没生成好,就会导致生成的效果较差。因此,需要根据实际图片遍历图像元素并进行掩码。

 //  4.提取图片下方的水印,制作掩模图像
        Mat mask = Mat::zeros(src.size(), CV_8U);
        int start_x = 0.8 * src.rows;
        int end_x = src.rows;
        int start_y = 0.8 * src.cols -40;
        int end_y = src.cols;

        //遍历图像像素,提取出水印部分像素,制作掩模图像
        for (int i = start_x; i < end_x; i++)
        {
            uchar* data = binary.ptr<uchar>(i);
            for (int j = start_y; j < end_y; j++)
            {
                //cout << binary.ptr<uchar>(i)[j]<<" ";
                if (data[j] == 255)
                {
                    mask.at<uchar>(i, j) = 255;
                }
                
            }
            cout << endl;
        }
        imshow("掩膜图", mask);

image-20221011151848057

1.5 膨胀

直接使用提取出的二值掩模进行图像修复得到的结果,可以看出效果不是很好。原因是,提取出来的掩模未能覆盖完全待修复像素。故我们需要将掩模图像进行膨胀操作,扩大掩模范围。

//  5.将掩模进行膨胀,使其能够覆盖图像更大区域
        Mat kernel = getStructuringElement(MORPH_RECT, Size(6, 6));
        dilate(mask, mask, kernel);
        imshow("膨胀图", mask);

image-20221011152016720

getStructuringElement(int shape, Size ksize, Point anchor)
需要输入两个参数: 
一个是原始图像, 
一个被称为结构化元素或核,它是用来决定操作的性质的
getStructuringElement源码介绍看附录

1.6 修复

这里使用opencv自带的图像修复函数,图像修复技术原理是利用已被破坏的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,已达到图像修补的目的。

//  6.使用inpaint进行图像修复
        Mat result;
        inpaint(src, mask, result, 8, INPAINT_NS);
        imshow("image show", result);

image-20221011151815927

void inpaint( 
    InputArray src, 
    InputArray inpaintMask,
    OutputArray dst, 
    double inpaintRadius, 
    int flags );
第一个参数src,输入的单通道或三通道图像;
第二个参数inpaintMask,图像的掩码,单通道图像,大小跟原图像一致,inpaintMask图像上除了需要修复的部分之外其他部分的像素值全部为0;
第三个参数dst,输出的经过修复的图像;
第四个参数inpaintRadius,修复算法取的邻域半径,用于计算当前像素点的差值;
第五个参数flags,修复算法,有两种:INPAINT_NS 和I NPAINT_TELEA;

2、进阶版去水印

2.1 获取原图

//  1. 获取原图
    Mat dst = imread("E:\\img\\4.jpg");
    if (dst.empty())
    {
        cout << "No Image!" << endl;
        system("pause");
        return -1;
    }

image-20221011164923130

2.2 通过鼠标定位水印位置

void on_mouse(int event, int x, int y, int flags, void* userdata) {
    Mat dst = (*(Mat*)userdata).clone();
    Mat src(*(Mat*)userdata);
    
    char temp[16];
    //判断左键按下,记录起始点坐标
    if (event == EVENT_LBUTTONDOWN) {
        point1.x = x;
        point1.y = y;
    }
    else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {
        //计算需画的矩形
        //Rect rect(startP.x, startP.y, x - startP.x, y - startP.y);
        //在图像上画出矩形
        point2.x = x;
        point2.y = y;
        
        sprintf_s(temp, "(%d,%d)", x, y);//坐标
        putText(dst, temp, point2, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0, 255));//实时显示鼠标移动的坐标  
        rectangle(dst, point1, Point(x, y), Scalar(250, 0, 0), 2, 0);
        imshow("image show", dst);
    }
    //左键松开,在原图像画出矩形
    else if (event == EVENT_LBUTTONUP) {
        point2.x = x;
        point2.y = y;

        //Rect rect(point1.x, point1.y, x - point1.x, y - point1.y);
        rectangle(dst, point1, point2, Scalar(0, 0, 250), 2, 0);
        imshow("image show", dst);
       
    }

}

(1)按住左键进行移动,此时会显示当前坐标信息以及当前选中的区域(用蓝色标出)。

image-20221011164959723

(2)释放左键,表示已经选好要去水印的区域,此时用红色矩形框标出。

image-20221011165029112

2.3 使用周围像素进行替换

选好区域后,在区域内进行像素替换,使用周围像素进行替换,偏移方向和距离可根据实际情况自定义。

 //对选中区域进行像素替换,偏移3个像素,根据实际情况调节
for (int i = min(point1.y, point2.y); i < max(point2.y, point1.y); i++)
{
    for (int j = min(point1.x, point2.x); j < max(point2.x, point1.x); j++)
    {

        src.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i, j - 3)[0];
        src.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i, j - 3)[1];
        src.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i, j - 3)[2];


    }
}

imshow("imagg", src);

image-20221011165048270

2.4 对局部区域进行平滑处理

对替换好像素的区域进行平滑处理,去除噪声

  //对选中区域周围进行平滑处理
Mat imageroi = src(Range(point1.y - 3, point2.y + 3), Range(point1.x - 3, point2.x + 3));
GaussianBlur(imageroi, imageroi, Size(15, 15), 0, 0);

image-20221011165110215

2.5 将平滑结果粘贴到替换区域

使用copyTo将平滑后的区域粘贴到原图像上。

Mat img1 = (src).clone();
Mat imgdst = img1(Rect(point1.x - 3,point1.y - 3, point2.x - point1.x, point2.y - point1.y));
imageroi.copyTo(imgdst);

image-20221011165123051

2.6 美颜(非必须)

对整体图像双边滤波(对人像有美颜效果)。

 //对整体图像双边滤波(对人像有美颜效果)
bilateralFilter(img1, dst, 15, 30, 5);

image-20221011165141457

附录

getStructuringElement源码介绍

cv::Mat cv::getStructuringElement(int shape, Size ksize, Point anchor)
{
    int i, j;
    int r = 0, c = 0;
    double inv_r2 = 0;

    CV_Assert( shape == MORPH_RECT || shape == MORPH_CROSS || shape == MORPH_ELLIPSE );        //目前支持三种形状的单元创建: 矩形, 十字形, 椭圆形;

    anchor = normalizeAnchor(anchor, ksize);                    //当默认为-1,-1时, 计算anchor;

    if( ksize == Size(1,1) )                  //当给定大小为1,1时,表明是一个点, 可以用矩形来表示;
        shape = MORPH_RECT;

    if( shape == MORPH_ELLIPSE )               //椭圆;
    {
        r = ksize.height/2;
        c = ksize.width/2;
        inv_r2 = r ? 1./((double)r*r) : 0;
    }

    Mat elem(ksize, CV_8U);

    for( i = 0; i < ksize.height; i++ )                    //对每一行,计算0,1的范围;
    {
        uchar* ptr = elem.ptr(i);
        int j1 = 0, j2 = 0;

        if( shape == MORPH_RECT || (shape == MORPH_CROSS && i == anchor.y) )        //矩形,或十字y锚点时  j2为ksize.width;
            j2 = ksize.width;
        else if( shape == MORPH_CROSS )
            j1 = anchor.x, j2 = j1 + 1;
        else                                               //椭圆;
        {
            int dy = i - r;
            if( std::abs(dy) <= r )
            {
                int dx = saturate_cast<int>(c*std::sqrt((r*r - dy*dy)*inv_r2));        //计算得到x的偏移;
                j1 = std::max( c - dx, 0 );
                j2 = std::min( c + dx + 1, ksize.width );
            }
        }

        for( j = 0; j < j1; j++ )                //从这三个for可以看出, (0,j1)之间为 0,  (j1, j2)之间为1,  (j2, ksize.width)之间为0;
            ptr[j] = 0;
        for( ; j < j2; j++ )
            ptr[j] = 1;
        for( ; j < ksize.width; j++ )
            ptr[j] = 0;
    }

    return elem;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_43784519/article/details/127267579

智能推荐

分享按键精灵中使用大漠插件做后台脚本_按键精灵大漠插件使用教程_豪猪不挡道的博客-程序员资料

前言– 也许大家在玩手游的时候都曾碰到过诸如 刷装备,刷怪升级的问题,除了充钱而只有那些重复性的东西才能让你变强,我也不例外。大学期间曾迷恋上阴阳师这个游戏。期间曾网上各种搜索自动刷装备的脚本,想以此来让自己轻松一些。不过免费的终究只是免费,被检测抓进三天小黑屋也是见怪不怪了。后来,偶然搜到了一个脚本群。得到了一些启发,也是因为自己电脑已经带不动作者的脚本了,才放弃使用。后来,在同学的帮助下, ...

Linux 性能优化的全景指南,可能都在这里了,建议收藏~_公众号:ITIL之家的博客-程序员资料

更多专业文档请访问 www.itilzj.comLinux 性能优化性能优化性能指标高并发和响应快对应着性能优化的两个核心指标:吞吐和延时应用负载角度:直接影响了产品终端的用户体验系统资源...

ubuntu平台下ffmpeg的编译安装方法_FISH_LJZ的博客-程序员资料

FFmpeg是一套可以用来记录、转换数字音视频,并能将其转化为流的开源计算机程序。它包括了目前领先的音/视频编码库libavcodec等。libavformat :用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能; libavcodec :用于各种类型声音/图像编解码; libavutil :包含一些公共的工具函数; libsws

FreeType2使用总结_mainn的博客-程序员资料

一、FreeType2简介1. 是一个免费、开源、可移植且高质量的字体引擎;2. 支持多种字体格式文件,并提供了统一的访问接口;3. 支持单色位图、反走样位图渲染,这使字体显示质量达到Mac的水平;4. 采用面向对象思想设计,用户可以灵活的根据需要裁剪。 二、FreeType2字形约定2.1 基本概念字形:  字符映像叫做字形,单个字符能够有多个不同的映像,即多...

ros中的velocity smoother详细分析_sunyoop的博客-程序员资料

smoother : 是一个速度平滑控制器,用来防止robot navigation的速度/转速过快,加速度/快减速过大smoother针对navigation或者其他一些例子,robot有一个命令选择node cmd_vel_mux,防止robot被多个ros app下发的运动命令控制从而出现运动问题。在move_base节点启动前启动smoother。velocity_smoother.launch.xml参数配置文件smoothrt.yaml虽然在move_base脚本中启动,但smooth

boost的递归锁_weixin_30411997的博客-程序员资料

引用之前发表过的文章:http://lajabs.blog.cd/?p=342针对如下错误:terminate called after throwing an instance of 'boost::exception_detail::clone_impl&lt;boost::exception_detail::error_info_injector &gt;'what(): boost:...

随便推点

win10修改ntp服务器地址,win10怎么设置ntp服务器地址_白头如新倾盖如故的博客-程序员资料

win10怎么设置ntp服务器地址 内容精选换一换以NTP服务器、DNS服务器的操作系统均为SUSE为例:登录Linux弹性云服务器。执行以下命令,切换至root用户。sudo su -sudo su -执行以下命令,编辑ntp.conf文件。vim /etc/ntp.confvim /etc/ntp.conf添加以下语句,配置NTP服务器。server NTP服务器域名或IP地址示例:有,该NT...

各个大数据相关框架启动与停止的命令_东心十的博客-程序员资料

zookeeper:三个节点上都执行: zkServer.sh start 启动单个节点的zk服务查看zk进程是否存在:jps ,发现一个进程 QuorumPeerMainzkServer.sh status 查看该zk服务器是follower还是leader。hdfs:start-dfs.shstop-dfs.sh查看hdfs进程是否存在:jpsbin/hdfs zk...

mybatis手动切换数据库_mybatis多数据库切换,(动态数据源)。_达布斯的博客-程序员资料

项目中将一个库的某些标的某些数据保存到另一个库。使用spring的aop编程动态切换数据源,代码如下,以备下次用到!1.先将两个数据库连接,创建两个数据源,交于spring管理!class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"&gt;class="com.alibaba....

【linux】bg、fg、jobs命令的学习,以及ctrl z/c、nohup和&_程序边界的博客-程序员资料

文章目录一、引入二、命令学习作业与进程的区别与联系:nohup 和 &amp; 的区别和联系fg命令_Linux fg 命令用法详解:将后台作业放到前台终端运行bg命令_Linux bg 命令用法详解:用于将作业放到后台运行jobs命令_Linux jobs 命令用法详解:显示Linux中的任务列表及任务状态一、引入这两个命令的学习是在使用 vim 不小心ctrl + z了,然后百度查到了fg命令。。。二、命令学习Linux下的fg和bg命令是进程的前后台调度命令,即将指定号码(非

资料 | 2021年开源SLAM算法集锦_3D视觉工坊的博客-程序员资料

本文汇总2021年公布的SLAM相关工作,后台回复“2021SLAM”可获得本文全部论文。1.TANDEM:Tracking and Dense Mapping in Real-time ...

python与java 时间戳的区别与转换_free0006的博客-程序员资料

和java不同的是在python下获取的时间戳是有小数点的浮点数,而在java中用new Date()获取的时间戳是一个整数,那么如果你想对JAVA生成的时间戳转换成python的话除以1000就可以了 获取当前时间的时间戳:import timetime.time() 获取特定时间的时间戳:import datetime,time  s=datetime.dat...

推荐文章

热门文章

相关标签