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

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值