OpenCV数字图像处理实战一:去水印(C++)_opencv去水印-程序员宅基地

技术标签: 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 x86-64 IOMMU详解(六)——Intel IOMMU参与下的DMA Coherent Mapping流程-程序员宅基地

文章浏览阅读3.2k次,点赞6次,收藏19次。在上一篇文章中,我们详细介绍了Intel IOMMU的初始化流程,并耗费大量笔墨讲述了此过程中Intel IOMMU与SWIOTLB二虎相争的故事。最终,SWIOTLB被禁用,而Intel IOMMU得以保留。现在,所有的DMA操作,都要经由Intel IOMMU了。本文将介绍Intel IOMMU在DMA Coherent Mapping过程中的作用。_linux x86-64 iommu详解

vue2.0实现富文本编辑及文本内容展示_vue显示富文本内容-程序员宅基地

文章浏览阅读1.4w次,点赞4次,收藏40次。vue2.0实现富文本编辑及文本内容展示_vue显示富文本内容

opentsDB单机版安装_opentsdb单机安装-程序员宅基地

文章浏览阅读694次。opentsDB单机版安装一、jdk安装1.下载https://www.oracle.com/technetwork/java/javase/downloads/index.html2.利用SecureCRT对服务器上传jdk,解压下载的jdk1.8.0_131tar -zxvf jdk-8u131-linux-x64.tar.gz -C /usr/local3.配置环境变量vi /..._opentsdb单机安装

【Latex】机器学习中的主要符号 LaTeX_latex sup-程序员宅基地

文章浏览阅读835次,点赞2次,收藏2次。\documentclass{article}\usepackage{ctex}\usepackage{amsmath}\usepackage{amssymb}\usepackage{wasysym}\usepackage{booktabs}\usepackage{fancyhdr} \pagestyle{fancy} \lhead{} \chead{} \rhead{..._latex sup

加拿大要把AI带上飞机,他们都准备干什么?-程序员宅基地

文章浏览阅读370次。随着旅客的日益增加、线路的不断开辟,空中航线变得愈加繁忙。相应的,航空公司之间的竞争日趋激烈,对飞机的检修维护等工作更显得不可开交。据国际航空运输协会(IATA)的数据显...

cmd/go: unsupported GOOS/GOARCH pair linux /amd64-程序员宅基地

文章浏览阅读7.8k次,点赞7次,收藏2次。windowds下编译go项目,执行如下操作时:SET CGO_ENABLED=0set GOARCH=amd64set GOOS=linuxgo build main.goset GOOS=linux这个操作的linux后面带了空格,编译器不能自动去掉空格,导致编译不过去。结束!..._go: unsupported goos/goarch pair linux /amd64

随便推点

tvp5150 若干问题,很好的解答_1.8v系统电压多高会复位-程序员宅基地

文章浏览阅读1.1k次。作者:德州仪器半导体技术(上海)有限公司 通用DSP 技术应用工程师 喻云峰1.简介TVP5150系列是一颗使用简易,超低功耗,封装极小的数字视频解码器。使用单一14.31818MHz时钟就可以实现PAL/NTSC/SECAM各种制式的解码,输出8-bit ITU-R BT.656数据,也可输出分离同步。MCU通过标准I2C接口控制TVP5150的诸多参数,比如色调,对比度,亮度,_1.8v系统电压多高会复位

matlab函数定义和调用-程序员宅基地

文章浏览阅读2.9w次,点赞13次,收藏87次。Matlab函数函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。你已经知道Matlab提供了许多内建函数,比如disp()。但你也可以自己创建函数,这被叫做用户自定义函数matlabdisp(‘hello world’)hello world总的来说,自定义函数分为两步:即定义函数和调用函数。定义一个函数你可以定义一个由自己想要功能的函数,以下是简单的规则:函数代码块以 function关键词开头,后接输出变量和函数标识_matlab函数定义和调用

【JMeter4.0】安装及运行(windows环境)_jemeter 4.0 使用jdk20-程序员宅基地

文章浏览阅读975次。安装JDK安装及配置安装对应版本的java环境,配置好环境变量。版本对应关系参考下表:JMeter版本JDK版本4.01.8 or 1.93.2/3.31.8+3.0/3.11.7+JDK环境变量配置:“我的电脑”属性-&amp;amp;gt;高级-&amp;amp;gt;环境变量-&amp;amp;gt;在系统变量中添加以下变量及对应变量值变量名变量值_jemeter 4.0 使用jdk20

Python几个国内镜像_python镜像-程序员宅基地

文章浏览阅读2.5w次,点赞3次,收藏21次。Python国内镜像地址:1.阿里云:https://mirrors.aliyun.com/pypi/simple/2.豆瓣:https://pypi.douban.com/simple/3.清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/(推荐)4.中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/5.华中理工大学:http://pypi.hustunique.com/6.山东理工大学:http://py_python镜像

TCP Thin-Stream连接_tcp_thin_linear_timeouts-程序员宅基地

文章浏览阅读953次。Thin-stream属性,意味着应用程序以很低的速率发送数据,致使TCP等传输协议的重传机制不能有效的运行。一些场景(类似于在线游戏,控制系统,股票交易等)中,用户体验取决于数据的发送时延,报文丢失对于服务质量来说是灾难性的。极大的时延是由于TCP依赖于应用程序新的报文的发送,进而通过快速重传来启动丢失报文的重传,而不用等待较长时间的RTO超时。以上提到的时间敏感的交互应用,通常是会产生thi..._tcp_thin_linear_timeouts

linux下查看端口占用情况、查看所有tcp端口情况_linux 机器如何查看大量处于tcp_wait 的端口是哪个-程序员宅基地

文章浏览阅读2w次,点赞3次,收藏17次。1、linux下查看所有占用端口情况netstat -ntlp2、查看所有某个端口使用情况,如80端口。netstat -ntulp |grep 803、查看一台服务器上面哪些服务及端口。netstat -lanp4、查看一个服务有几个端口,比如要查看mysqld。ps -ef |grep mysqldnetstat命令各个参数说明如下:-a..._linux 机器如何查看大量处于tcp_wait 的端口是哪个

推荐文章

热门文章

相关标签