opencv实现的图像缩放

opencv实现的图像缩放

利用opencv实现图像缩放有两种方法:

一、使用opencv的内置方法resize重新定义一个图片的大小从而实现缩放。

二、自己写一个原始的像素提取与内插的方法实现图片的缩放。

下面是第一种方法resize:

首先要知道怎么读取一张图片的信息:

 

通过print(imgInfo)我们可以知道这个图片的宽高信息和像素组成

接下来代码的设计如下

 

代码如下:

 

import cv2
img = cv2.imread('image0.jpg',1)
imgInfo = img.shape
print(imgInfo)
height = imgInfo[0]
width = imgInfo[1]
mode = imgInfo[2]
dstHeight = int(height*0.5)
dstWidth = int(width*0.5)
dst = cv2.resize(img,(dstWidth,dstHeight))
cv2.imshow('image',dst)
cv2.waitKey(0)

 

 

 

这里的imgInfo[0]代表的高度信息(列),行同理

新的宽高信息的定义:dstHeight = int(height*0.5) //缩小一半

最后调用resize方法定义新的图片宽高实现缩放。

其中resize方法的第一个参数为img,是缩放的对象。

 

 

 

第二种方法:

自定义原始图片像素提取与内插的方法实现图片的缩放。

缩小:这里是抽取原图像的缩放倍数对应的整数点来组成新图片实现图片的缩小。

放大:则要做内插处理,内插方法,1.最近邻插值法,2.双线性插值法。 

      缩小,抽取像素点的思路如下

代码如下;

 

import cv2
import numpy as np
img = cv2.imread('image0.jpg',1)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
dstHeight = int(height/2)
dstWidth = int(width/2)
dstImage = np.zeros((dstHeight,dstWidth,3),np.uint8)#0-255 
for i in range(0,dstHeight):#行
    for j in range(0,dstWidth):#列 
        iNew = int(i*(height*1.0/dstHeight))
        jNew = int(j*(width*1.0/dstWidth))
        dstImage[i,j] = img[iNew,jNew]
cv2.imshow('dst',dstImage)
cv2.waitKey(0)

 

放大图片的内插方法:

一、最近邻插值
本文链接:图像的插值算法之最近邻插值 - linqianbi的博客 - CSDN博客

 参考原文链接:    https://www.wengbi.com/thread_86596_1.html

 

设i+u, j+v(i, j为正整数, u, v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰度的值 f(i+u, j+v)。
如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,

落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。
优点:最邻近元法计算量较小,

缺点:但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。

 

 

二、双线性插值

 原文地址:https://blog.csdn.net/ccblogger/article/details/72918354

      假设源图像大小为mxn,目标图像为axb。那么两幅图像的边长比分别为:m/a和n/b。注意,通常这个比例不是整数,编程存储的时候要用浮点型。目标图像的第(i,j)个像素点(i行j列)可以通过边长比对应回源图像。其对应坐标为(i*m/a,j*n/b)。显然,这个对应坐标一般来说不是整数,而非整数的坐标是无法在图像这种离散数据上使用的。双线性插值通过寻找距离这个对应坐标最近的四个像素点,来计算该点的值(灰度值或者RGB值)。

   下面的文字和公式过于抽象,这里先举个例子帮助理解,在看下面的公式相对容易

首先建立一个左上角为坐标原点的坐标系,对应的X轴和Y轴如下(注意,后面的建系方法不一样,这里我只讲双线性插值原理)

该方法主要是根据到前后或者上下两点的距离来确定该点的坐标,这里我把这个方法叫做:比例距离反乘法。

按照这个原理,同样也可以根据该点到上下或前后两点的距离比列关系,来求该点与四点的像素线性关系。

 正文:

  若图像为灰度图像,那么(i,j)点的灰度值的数学计算模型是:

 

f(x,y)=b1+b2x+b3y+b4xy

 

其中b1,b2,b3,b4是相关的系数。关于其的计算过程如下如下:

 

      如图,已知Q12,Q22,Q11,Q21,但是要插值的点为P点,这就要用双线性插值了,首先在x轴方向上,对R1和R2两个点进行插值,这个很简单,然后根据R1和R2对P点进行插值,这就是所谓的双线性插值。

 

clip_image001

 

附:维基百科--双线性插值:

 

      双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。

 

假如我们想得到未知函数 f 在点 P=\left( x, y\right) 的值,假设我们已知函数 f 在 Q_{11} = \left( x_1, y_1 \right)Q_{12} = \left( x_1, y_2 \right)Q_{21} = \left( x_2, y_1 \right), 及 Q_{22} = \left( x_2, y_2 \right) 四个点的值。

 

首先在 x 方向进行线性插值,得到

 

f(R_1) \approx \frac{x_2-x}{x_2-x_1} f(Q_{11}) + \frac{x-x_1}{x_2-x_1} f(Q_{21}) \quad\mbox{Where}\quad R_1 = (x,y_1),
f(R_2) \approx \frac{x_2-x}{x_2-x_1} f(Q_{12}) + \frac{x-x_1}{x_2-x_1} f(Q_{22}) \quad\mbox{Where}\quad R_2 = (x,y_2).

 

然后在 y 方向进行线性插值,得到

 

f(P) \approx \frac{y_2-y}{y_2-y_1} f(R_1) + \frac{y-y_1}{y_2-y_1} f(R_2).

 

这样就得到所要的结果 f \left( x, y \right),

 

f(x,y) \approx \frac{f(Q_{11})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y_2-y) + \frac{f(Q_{21})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y_2-y)
+ \frac{f(Q_{12})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y-y_1) + \frac{f(Q_{22})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y-y_1).

 

如果选择一个坐标系统使得 f 的四个已知点坐标分别为 (0, 0)、(0, 1)、(1, 0) 和 (1, 1),那么插值公式就可以化简为

 

f(x,y) \approx f(0,0) \, (1-x)(1-y) + f(1,0) \, x(1-y) + f(0,1) \, (1-x)y + f(1,1) xy.

 

或者用矩阵运算表示为

 

f(x,y) \approx \begin{bmatrix}1-x & x \end{bmatrix} \begin{bmatrix}f(0,0) & f(0,1) \\f(1,0) & f(1,1) \end{bmatrix} \begin{bmatrix}1-y \\y \end{bmatrix}

 

这种插值方法的结果通常不是线性的,线性插值的结果与插值的顺序无关。首先进行 y 方向的插值,然后进行 x 方向的插值,所得到的结果是一样的。

 

OpenCV和Matlab中的双线性插值

 

   这部分的前提是,你已经明白什么是双线性插值并且在给定源图像和目标图像尺寸的情况下,可以用笔计算出目标图像某个像素点的值。当然,最好的情况是你已经用某种语言实现了网上一大堆博客上原创或转载的双线性插值算法,然后发现计算出来的结果和matlab、openCV对应的resize()函数得到的结果完全不一样。

 

那这个究竟是怎么回事呢?

 

其实答案很简单,就是坐标系的选择问题,或者说源图像和目标图像之间的对应问题。

 

按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下:

 

 

只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。

 

最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlab和openCV的做法。如下图:

 

如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,

 

int x=(i+0.5)*m/a-0.5

 

int y=(j+0.5)*n/b-0.5

 

代替

 

int x=i*m/a

 

int y=j*n/b

 

利用上述公式,将得到正确的双线性插值结果。

 

 uchar* dataDst = matDst1.data;
    int stepDst = matDst1.step;
    uchar* dataSrc = matSrc.data;
    int stepSrc = matSrc.step;
    int iWidthSrc = matSrc.cols;
    int iHiehgtSrc = matSrc.rows;

    for (int j = 0; j < matDst1.rows; ++j)
    {
        float fy = (float)((j + 0.5) * scale_y - 0.5);
        int sy = cvFloor(fy);
        fy -= sy;
        sy = std::min(sy, iHiehgtSrc - 2);
        sy = std::max(0, sy);

        short cbufy[2];
        cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);
        cbufy[1] = 2048 - cbufy[0];

        for (int i = 0; i < matDst1.cols; ++i)
        {
            float fx = (float)((i + 0.5) * scale_x - 0.5);
            int sx = cvFloor(fx);
            fx -= sx;

            if (sx < 0) {
                fx = 0, sx = 0;
            }
            if (sx >= iWidthSrc - 1) {
                fx = 0, sx = iWidthSrc - 2;
            }

            short cbufx[2];
            cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
            cbufx[1] = 2048 - cbufx[0];

            for (int k = 0; k < matSrc.channels(); ++k)
            {
                *(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + 
                    *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;
            }
        }
    }
    cv::imwrite("linear_1.jpg", matDst1);

    cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);
    cv::imwrite("linear_2.jpg", matDst2);

 

 

 

 

 

posted @ 2019-07-17 21:15  量子与太极  阅读(6180)  评论(0编辑  收藏  举报