PIL: Python 图像处理类库



In [1]:
%pylab inline
from sympy import init_printing
init_printing(use_latex='mathjax')
import os
os.chdir(os.path.join(os.curdir,'data'))


Populating the interactive namespace from numpy and matplotlib

In [2]:
from PIL import Image
pil_im = Image.open('empire.jpg')
pil_im


Out[2]:

In [3]:
import cv2
cv_im = cv2.imread('empire.jpg')
cv_im = cv2.cvtColor(cv_im,cv2.COLOR_BGR2RGB) # 转为RGB用于matplot显示
imshow(cv_im)


Out[3]:
<matplotlib.image.AxesImage at 0xbf1e4e0>

In [4]:
Image.open('empire.jpg').convert('L')


Out[4]:

In [5]:
thumbnail = pil_im.copy()
thumbnail.thumbnail((128,128))  # 缩略图
thumbnail


Out[5]:

In [6]:
box = (100,100,400,400)  # (左,上,右,下)
region = pil_im.crop(box)  # 剪裁指定区域
region = region.transpose(Image.ROTATE_180)  # 转180度
tmp = pil_im.copy()
tmp.paste(region,box)  # 黏贴回去
tmp


Out[6]:

In [7]:
pil_im.resize((128,128))  # 生成指定大小的新图像


Out[7]:

In [8]:
pil_im.rotate(45).resize((128,128))  # 逆时针转45度


Out[8]:

Matplotlib



In [9]:
%pylab inline


Populating the interactive namespace from numpy and matplotlib
WARNING: pylab import has clobbered these variables: ['box']
`%matplotlib` prevents importing * from pylab and numpy

In [10]:
from PIL import Image
from pylab import *

im = array(Image.open('empire.jpg'))

imshow(im)

x = [100, 100, 400, 400]
y = [200, 500, 200, 500]
plot(x, y, 'r*')

plot(x[:2], y[:2])

#axis('off')

title('Plotting: "empire.jpg"')
show()



In [11]:
from PIL import Image
from pylab import *

im = array(Image.open('empire.jpg'))

imshow(im)

x = [100, 100, 400, 400]
y = [200, 500, 200, 500]
plot(x, y, 'r*')

plot(x[:2], y[:2])

axis('off')

title('Plotting: "empire.jpg"')
show()


绘图格式命令:

  • b : blue. 蓝
  • g : green 绿
  • r : red. 红
  • c : cyan. 青
  • m : magenta. 品红
  • y : yellow. 黄
  • k : black 黑
  • w : white 白

  • - : 实线

  • -- : 虚线
  • : : 点线

  • . : 点

  • o : 圈
  • s : 正方形
  • * : 星形
  • + : 加号
  • x : 叉号

直方图



In [12]:
im = pil_im.convert('L')
figure()
gray()  # 指定为黑白色
contour(im, orign='image')  # 轮廓图像
axis('equal')  # 不变形
axis('off')  # 不显示


Out[12]:
$$\left ( 0.0, \quad 600.0, \quad 0.0, \quad 800.0\right )$$

In [13]:
figure()
np_im = np.array(im)
hist(np_im.flatten(),128)  # 直方图
xlim([0,256])
show()


OpenCV 直方图

cv2.cvtColor

cv2.calcHist


In [14]:
# cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) -> hist
import cv2
im = cv2.cvtColor(array(pil_im),cv2.COLOR_BGR2GRAY)  # 转为灰度图像
hist_item = cv2.calcHist([im],[0],None,[128],[0,256]) # 计算直方图
xlim([0,256])
bar(array([x for x in range(128)])*2,hist_item)


Out[14]:
<Container object of 128 artists>

In [15]:
hist_item = cv2.calcHist([im],[0],None,[256],[0,256]) # 计算直方图
cv2.normalize(hist_item,hist_item,0,255,cv2.NORM_MINMAX) # 归一化到 [0,255]的范围
hist_item=np.int32(np.around(hist_item))  # 像素取整
ylim([0,256])
xlim([0,256])
bar([x for x in range(256)],hist_item)


Out[15]:
<Container object of 256 artists>

In [16]:
## 交互式标注
# im = pil_im.copy()
# x = ginput(3)  # 点击3次选择3个点
# x

Numpy



In [17]:
im = np.array(pil_im.copy())
i,j,k = 0,0,0
im[i,j,k]  # 坐标 i行,j列,颜色通道 k


Out[17]:
88

In [18]:
# 灰度变换
im = np.array(pil_im.copy().convert('L'))
Image.fromarray(255 - im) # 对图像反相处理


Out[18]:

In [19]:
Image.fromarray(uint8((100.0/255.0)*im + 100))  # 将像素值变换到 100..200


Out[19]:

In [20]:
Image.fromarray(uint8(255.0 * (im/255.0)**2))  # 将图像像素求平方后的图像


Out[20]:

图像缩放



In [21]:
def imresize(im,sz):
    pil_im = Image.fromarray(uint8(im))
    return array(pil_im.resize(sz))

直方图均衡化



In [22]:
def histeq(im,nbr_bins=256):
    """    Histogram equalization of a grayscale image. """
    if type(im) == Image.Image:
        im = array(im)
    # 计算直方图
    imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
    cdf = imhist.cumsum() # 计算分布函数
    cdf = 255 * cdf / cdf[-1] # normalize 归一化
    
    # 用积累分布函数的线性插值,计算新的像素值
    im2 = interp(im.flatten(),bins[:-1],cdf)
    im2 = Image.fromarray(uint8(im2.reshape(im.shape)))
    
    return im2,cdf

In [23]:
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

In [24]:
im = pil_im.copy()
im2, cdf = histeq(im)

In [25]:
axis('off')
title(u'原始图像', fontproperties=font)
imshow(im)


Out[25]:
<matplotlib.image.AxesImage at 0x1105ba20>

In [26]:
axis('off')
title(u'直方图均衡化后的图像', fontproperties=font)
imshow(im2)


Out[26]:
<matplotlib.image.AxesImage at 0xc30dd68>

In [27]:
axis('off')
title(u'原始直方图', fontproperties=font)
hist(array(im).flatten(), 128, normed=True)
show()



In [28]:
axis('off')
title(u'均衡化后的直方图', fontproperties=font)
hist(array(im2).flatten(), 128, normed=True)
show()


OpenCV 版直方图均衡化



In [29]:
im = cv2.cvtColor(array(pil_im),cv2.COLOR_BGR2GRAY)  # 转为灰度图像
imshow(cv2.equalizeHist(im))


Out[29]:
<matplotlib.image.AxesImage at 0x12a73ef0>

图像平均


用于减少噪声的简单方式,常用于艺术特效


In [30]:
def compute_average(imlist):
    """    Compute the average of a list of images. """
    
    # open first image and make into array of type float
    averageim = array(Image.open(imlist[0]), 'f') 

    skipped = 0
    
    for imname in imlist[1:]:
        try: 
            averageim += array(Image.open(imname))
        except:
            print imname + "...skipped"  
            skipped += 1

    averageim /= (len(imlist) - skipped)
    
    # return average as uint8
    return array(averageim, 'uint8')

主成份分析(PCA, Principal Component Analysis)


  • PCA产生的投影矩阵可以看成将原始坐标变换到现有坐标,坐标系中各个坐标按照重要性递减排列

In [31]:
def pca(X):
    """    主成份分析
        输入: X, 矩阵,每一行为一条训练数据(训练图像)
        返回: 投影矩阵(按照维度的重要性排序),方差,均值
    """
    
    # 获取维数
    num_data,dim = X.shape
    
    # 数据中心化
    mean_X = X.mean(axis=0)
    X = X - mean_X
    
    if dim>num_data:
        # PCA - 使用紧致技巧
        M = dot(X,X.T) # 协方差矩阵 covariance matrix
        e,EV = linalg.eigh(M) # 特征值和特征向量 eigenvalues and eigenvectors
        tmp = dot(X.T,EV).T # this is the compact trick(紧致技巧)
        V = tmp[::-1] # 由于最后的特征向量是我们需要的,所以需要进行逆转(reverse)
        S = sqrt(e)[::-1] # 由于特征值是递增排序的,所以也要逆转
        for i in range(V.shape[1]):
            V[:,i] /= S
    else:
        # PCA - SVD 使用方法
        U,S,V = linalg.svd(X)
        V = V[:num_data] # 仅返回前 num_data 维的数据才合理
    
    # 返回: 投影矩阵,方差,均值
    return V,S,mean_X

In [32]:
import os
def get_imlist(path):
    """    Returns a list of filenames for 
        all jpg images in a directory. """
        
    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]

In [33]:
# 获取图像列表和他们的尺寸
imlist = get_imlist('a_thumbs')
im = array(Image.open(imlist[0]))  # open one image to get the size
m, n = im.shape[:2]  # get the size of the images
imnbr = len(imlist)  # get the number of images
print "The number of images is %d" % imnbr

# Create matrix to store(存储) all flattened images(压平的图像)
immatrix = array([array(Image.open(imname)).flatten() for imname in imlist], 'f')

# PCA降维
V, S, immean = pca(immatrix)

# 显示图像(均值,和前 7 维图像
figure()
gray()
subplot(2, 4, 1)
axis('off')
imshow(immean.reshape(m, n))
for i in range(7):
    subplot(2, 4, i+2)
    imshow(V[i].reshape(m, n))
    axis('off')
show()


The number of images is 2359

OpenCV 版

主要函数说明:

cv2.PCACompute(data[, mean[, eigenvectors[, maxComponents ]]]) -> mean, eigenvectors
cv2.PCAComputeVar(data, retainedVariance[, mean[, eigenvectors ]]) -> mean, eigenvectors
cv2.PCAProject(data, mean, eigenvectors[, result ]) -> result
cv2.PCABackProject(data, mean, eigenvectors[, result ]) -> result

其中:

  • data 是输入的数据集
  • mean 是均值向量
  • eigenvectors 是特征值向量
  • maxComponents 是保留的维数,默认为全部保存
  • retainedVariance 是PCA保留的向量百分比,即保留的个数自动计算,但至少为2个
  • PCAProject 将输入数据vec(该数据是用来提取PCA特征的原始数据)投影到PCA主成分空间中,返回每一个样本主成分特征组成的矩阵。因为经过PCA处理后,原始数据的维数降低了,因此原始数据集中的每一个样本的维数都变了,由改变后的样本集就组成了本函数的返回值。
  • PCABackProject 函数的参数vec为经过PCA投影降维(PCAProject)过后的矩阵。用来重构原始数据集

主要函数: cv2.PCACompute cv2.PCAComputeVar cv2.PCAProject cv2.PCABackProject


In [34]:
import cv2
mean, eig = cv2.PCACompute(immatrix,None)

# 显示图像(均值,和前 7 维图像
figure()
gray()
subplot(2, 4, 1)
axis('off')
imshow(mean.reshape(m, n))
for i in range(7):
    subplot(2, 4, i+2)
    imshow(eig[i].reshape(m, n))
    axis('off')
show()


Scipy


高斯模糊



In [35]:
from PIL import Image
from pylab import *
from scipy.ndimage import filters
im = array(Image.open('empire.jpg'))
im2 = filters.gaussian_filter(im,5)  # 标准差为5
axis('off')
imshow(im2)


Out[35]:
<matplotlib.image.AxesImage at 0x11b7bf98>

OpenCV 模糊

# 高斯模糊
cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType ]]]) -> dst
# 均值模糊
cv2.medianBlur(src, ksize[, dst ]) -> dst
# 通用模糊
cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType ]]]]) -> dst

# 获取 kernel
cv2.getGaussianKernel(ksize, sigma[, ktype ]) -> retval
cv2.getDerivKernels(dx, dy, ksize[, kx[, ky[, normalize[, ktype ]]]]) -> kx, ky

ksize : kernel size. ksize.width 和 ksize.height 可以不同,但必须为正的奇数,当其为0时从sigma计算得到

ddepth : 图像深度

通用模糊 filter2D:

$$ dst(x,y) = \sum_{ 0 \le x' < kernel.cols} \sum_{0 \le y' < kernel.rows} {kernel(x',y')*src(x + x' - anchor.x, y+y'-anchor.y)} $$

主要函数: cv2.GaussianBlur cv2.medianBlur cv2.filter2D cv2.getGaussianKernel cv2.getDerivKernels


In [36]:
import cv2
im2 = cv2.GaussianBlur(im,(0,0),sigmaX=5,sigmaY=5)
axis('off')
imshow(im2)


Out[36]:
<matplotlib.image.AxesImage at 0x12924470>

图像导数


Prewitt 滤波器:

$$ D_x = \left[\begin{matrix}-1 & 0 & 1\\-1 & 0 & 1\\-1 & 0 & 1\end{matrix}\right] \\ D_y = \left[\begin{matrix}-1 & -1 & -1\\0 & 0 & 0\\1 & 1 & 1\end{matrix}\right] $$

Sobel 滤波器:

$$ D_x = \left[\begin{matrix}-1 & 0 & 1\\-2 & 0 & 2\\-1 & 0 & 1\end{matrix}\right] \\ D_y = \left[\begin{matrix}-1 & -2 & -1\\0 & 0 & 0\\1 & 2 & 1\end{matrix}\right] $$

Sobel 滤波器


In [37]:
from scipy.ndimage import filters
from pylab import *

# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

im = array(Image.open('empire.jpg').convert('L'))

axis('off')
title(u'(a)原图', fontproperties=font)
imshow(im)


Out[37]:
<matplotlib.image.AxesImage at 0x28a11828>

In [38]:
# Sobel derivative filters
imx = zeros(im.shape)
filters.sobel(im, 1, imx)
axis('off')
title(u'(b)x方向差分', fontproperties=font)
imshow(imx)


Out[38]:
<matplotlib.image.AxesImage at 0x11f4a1d0>

In [39]:
imy = zeros(im.shape)
filters.sobel(im, 0, imy)
axis('off')
title(u'(c)y方向差分', fontproperties=font)
imshow(imy)


Out[39]:
<matplotlib.image.AxesImage at 0xc4bdb38>

In [40]:
#mag = numpy.sqrt(imx**2 + imy**2)
mag = 255-numpy.sqrt(imx**2 + imy**2)
title(u'(d)梯度幅度', fontproperties=font)
axis('off')
imshow(mag)


Out[40]:
<matplotlib.image.AxesImage at 0x11ad6710>

OpenCV 求导 Sobel Laplacian

cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType ]]]]]) -> dst
cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType ]]]]]) -> dst

ddepth -> 图像深度

主要函数: cv2.Sobel cv2.Scharr cv2.Laplacian


In [41]:
imx = cv2.Sobel(im,0,1,0)
axis('off')
imshow(imx)


Out[41]:
<matplotlib.image.AxesImage at 0x11ba8208>

In [42]:
imy = cv2.Sobel(im,0,0,1)
axis('off')
imshow(imy)


Out[42]:
<matplotlib.image.AxesImage at 0x11ba8128>

In [43]:
mag = cv2.Sobel(im,0,1,1)
axis('off')
imshow(mag)


Out[43]:
<matplotlib.image.AxesImage at 0x128180b8>

Gauss Deriv 高斯导数


In [44]:
def imx(im, sigma):
    imgx = zeros(im.shape)
    filters.gaussian_filter(im, sigma, (0, 1), imgx)
    return imgx


def imy(im, sigma):
    imgy = zeros(im.shape)
    filters.gaussian_filter(im, sigma, (1, 0), imgy)
    return imgy


def mag(im, sigma):
    # there's also gaussian_gradient_magnitude()
    #mag = numpy.sqrt(imgx**2 + imgy**2)
    imgmag = 255 - numpy.sqrt(imgx ** 2 + imgy ** 2)
    return imgmag


im = array(Image.open('empire.jpg').convert('L'))
figure()
gray()

sigma = [2, 5, 10]

for i in  sigma:
    subplot(3, 4, 4*(sigma.index(i))+1)
    axis('off')
    imshow(im)
    imgx=imx(im, i)
    subplot(3, 4, 4*(sigma.index(i))+2)
    axis('off')
    imshow(imgx)
    imgy=imy(im, i)
    subplot(3, 4, 4*(sigma.index(i))+3)
    axis('off')
    imshow(imgy)
    imgmag=mag(im, i)
    subplot(3, 4, 4*(sigma.index(i))+4)
    axis('off')
    imshow(imgmag)


OpenCV Gauss Deriv


In [45]:
imx = cv2.GaussianBlur(im,(1,0),sigmaX=10,sigmaY=10)
axis('off')
imshow(imx)


Out[45]:
<matplotlib.image.AxesImage at 0x11f3fe80>

In [46]:
imy = cv2.GaussianBlur(im,(0,1),sigmaX=10,sigmaY=10)
axis('off')
imshow(imy)


Out[46]:
<matplotlib.image.AxesImage at 0x11f43550>

形态学:对象计数



In [47]:
from scipy.ndimage import measurements, morphology
from pylab import *

im = array(Image.open('houses.png').convert('L'))

imshow(im)
axis('off')
title(u'原图', fontproperties=font)


Out[47]:
<matplotlib.text.Text at 0x2fe7ddd8>

In [48]:
im = (im < 128)
labels, nbr_objects = measurements.label(im)
print "Number of objects:", nbr_objects

imshow(labels)
axis('off')
title(u'标记后的图', fontproperties=font)


Number of objects: 45
Out[48]:
<matplotlib.text.Text at 0x2ffb9dd8>

In [49]:
# morphology - opening to separate objects better
im_open = morphology.binary_opening(im, ones((9, 5)), iterations=2)

imshow(im_open)
axis('off')
title(u'开运算后的图像', fontproperties=font)


Out[49]:
<matplotlib.text.Text at 0x301f4a58>

In [50]:
labels_open, nbr_objects_open = measurements.label(im_open)
print "Number of objects:", nbr_objects_open

imshow(labels_open)
axis('off')
title(u'开运算后进行标记后的图像', fontproperties=font)


Number of objects: 48
Out[50]:
<matplotlib.text.Text at 0x30f7f438>

OpenCV 形态学运算

http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html#morphological-ops

cv2.erode cv2.dilate cv2.morphologyEx

# 腐蚀
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])  dst
# 膨胀
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])  dst
# 形态学操作
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])  dst
  • kernel 操作核,2d 奇数方阵?
  • anchor 锚, 默认为(-1,-1)即中心
  • iterations 迭代次数
  • borderType 边界类型?
    • cv2.borderInterpolate(p, len, borderType) → retva
  • borderValue 边界值? 在一个常数边界情况下的边界值
  • op 形态学类型:
    • MORPH_OPEN - 开操作, 先腐蚀后膨胀
    • MORPH_CLOSE - 闭操作, 先膨胀后腐蚀
    • MORPH_GRADIENT - 形态学梯度, 膨胀图与腐蚀图之差(边界)
    • MORPH_TOPHAT - “top hat” 礼帽, 原图减去开运算
    • MORPH_BLACKHAT - “black hat” 黑帽, 闭运算减去原图
    • MORPH_HITMISS - “hit and miss” 击中不击中,两次腐蚀,交集?

腐蚀:

$$ dst(x,y) = \max_{(x',y'):element(x',y') \ne 0}{src(x+x',y+y')} $$

膨胀:

$$ dst(x,y) = \min_{(x',y'):element(x',y') \ne 0}{src(x+x',y+y')} $$

实现连通域标记:

从OpenCV 3.0 开始有函数:

int connectedComponents(InputArray image, OutputArray labels, int connectivity=8, int ltype=CV_32S)

int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity=8, int ltype=CV_32S)

现在也可以用 findContours 实现标记

此外常用算法有两遍扫描(Two-Pass)或平面区域填充(Seed-Filling),或满水填充算法(cv2.floodFill)

cv2.floodFill(image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]])  retval, rect)

a、这里的扫描指的是按行或按列访问以便图像的所有像素,本文算法采用的是按行扫描方式;

b、图像记为B,为二值图像:前景像素(pixel value = 1),背景像素(pixel value = 0)

c、label从2开始计数;

d、像素相邻关系:4-领域、8-领域,本文算法采用4-邻域;

两遍扫描法 Two-Pass:

(1)第一次扫描:

   访问当前像素B(x,y),如果B(x,y) == 1:

   a、如果B(x,y)的领域中像素值都为0,则赋予B(x,y)一个新的label:

            label += 1, B(x,y) = label;

   b、如果B(x,y)的领域中有像素值 > 1的像素Neighbors:

           1)将Neighbors中的最小值赋予给B(x,y):

                      B(x,y) = min{Neighbors}

           2)记录Neighbors中各个值(label)之间的相等关系,即这些值(label)同属同一个连通区域;

                     labelSet[i] = { label_m, .., label_n },labelSet[i]中的所有label都属于同一个连通区域

                   (注:这里可以有多种实现方式,只要能够记录这些具有相等关系的label之间的关系即可)



(2)第二次扫描:

    访问当前像素B(x,y),如果B(x,y) > 1:

           a、找到与label = B(x,y)同属相等关系的一个最小label值,赋予给B(x,y);

   完成扫描后,图像中具有相同label值的像素就组成了同一个连通区域。

Two-Pass C++ 版本
//  Connected Component Analysis/Labeling By Two-Pass Algorithm 
//  Author:  www.icvpr.com  
//  Blog  :  http://blog.csdn.net/icvpr 
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>


void icvprCcaByTwoPass(const cv::Mat& _binImg, cv::Mat& _lableImg)
{
    // connected component analysis (4-component)
    // use two-pass algorithm
    // 1. first pass: label each foreground pixel with a label
    // 2. second pass: visit each labeled pixel and merge neighbor labels
    // 
    // foreground pixel: _binImg(x,y) = 1
    // background pixel: _binImg(x,y) = 0


    if (_binImg.empty() ||
        _binImg.type() != CV_8UC1)
    {
        return ;
    }

    // 1. first pass

    _lableImg.release() ;
    _binImg.convertTo(_lableImg, CV_32SC1) ;

    int label = 1 ;  // start by 2
    std::vector<int> labelSet ;
    labelSet.push_back(0) ;   // background: 0
    labelSet.push_back(1) ;   // foreground: 1

    int rows = _binImg.rows - 1 ;
    int cols = _binImg.cols - 1 ;
    for (int i = 1; i < rows; i++)
    {
        int* data_preRow = _lableImg.ptr<int>(i-1) ;
        int* data_curRow = _lableImg.ptr<int>(i) ;
        for (int j = 1; j < cols; j++)
        {
            if (data_curRow[j] == 1)
            {
                std::vector<int> neighborLabels ;
                neighborLabels.reserve(2) ;
                int leftPixel = data_curRow[j-1] ;
                int upPixel = data_preRow[j] ;
                if ( leftPixel > 1)
                {
                    neighborLabels.push_back(leftPixel) ;
                }
                if (upPixel > 1)
                {
                    neighborLabels.push_back(upPixel) ;
                }

                if (neighborLabels.empty())
                {
                    labelSet.push_back(++label) ;  // assign to a new label
                    data_curRow[j] = label ;
                    labelSet[label] = label ;
                }
                else
                {
                    std::sort(neighborLabels.begin(), neighborLabels.end()) ;
                    int smallestLabel = neighborLabels[0] ;  
                    data_curRow[j] = smallestLabel ;

                    // save equivalence
                    for (size_t k = 1; k < neighborLabels.size(); k++)
                    {
                        int tempLabel = neighborLabels[k] ;
                        int& oldSmallestLabel = labelSet[tempLabel] ;
                        if (oldSmallestLabel > smallestLabel)
                        {                           
                            labelSet[oldSmallestLabel] = smallestLabel ;
                            oldSmallestLabel = smallestLabel ;
                        }                       
                        else if (oldSmallestLabel < smallestLabel)
                        {
                            labelSet[smallestLabel] = oldSmallestLabel ;
                        }
                    }
                }               
            }
        }
    }

    // update equivalent labels
    // assigned with the smallest label in each equivalent label set
    for (size_t i = 2; i < labelSet.size(); i++)
    {
        int curLabel = labelSet[i] ;
        int preLabel = labelSet[curLabel] ;
        while (preLabel != curLabel)
        {
            curLabel = preLabel ;
            preLabel = labelSet[preLabel] ;
        }
        labelSet[i] = curLabel ;
    }


    // 2. second pass
    for (int i = 0; i < rows; i++)
    {
        int* data = _lableImg.ptr<int>(i) ;
        for (int j = 0; j < cols; j++)
        {
            int& pixelLabel = data[j] ;
            pixelLabel = labelSet[pixelLabel] ; 
        }
    }
}
Two-Pass Python版本

In [51]:
def icvprCcaByTwoPass(im):
    # 第一遍扫描
    lableImg = np.array(im,dtype=np.float32) # 因为标签的值可能超过 255
    rows, cols = im.shape
    label = 1 # 从2开始
    labelSet = []
    labelSet.append(0)  # 背景 0
    labelSet.append(1) # 前景  1
    
    for row in range(1,rows-1):
        perRow = lableImg[row-1,:]
        curRow = lableImg[row,:]
        for col in range(1,cols-1):
            if curRow[col] == 1:
                neighborLabels = []
                leftPixel = curRow[col-1]
                upPixel = perRow[col]
                if leftPixel > 1:
                    neighborLabels.append(leftPixel)
                if upPixel > 1:
                    neighborLabels.append(upPixel)
                if neighborLabels == []:
                    label += 1
                    labelSet.append(label)  # 应用一个新的标签
                    curRow[col] = label
                    #labelSet[label] = label  # 是不是多余? 不多余?
                else:
                    neighborLabels = sort(neighborLabels)
                    smallestLabel = neighborLabels[0]
                    smallestLabel = int(smallestLabel)
                    curRow[col] = smallestLabel
                    # 保存等价的标签
                    for k in range(1,len(neighborLabels)):
                        tempLabel = neighborLabels[k]
                        tempLabel = int(tempLabel)
                        oldSmallestLabel = labelSet[tempLabel]
                        oldSmallestLabel = int(oldSmallestLabel)
                        if oldSmallestLabel > smallestLabel:
                            labelSet[oldSmallestLabel] = smallestLabel
                            labelSet[tempLabel] = smallestLabel
                        elif oldSmallestLabel < smallestLabel:
                            labelSet[smallestLabel] = oldSmallestLabel
    # 更新等价的标签
    # 确保 labelSet[Label] 中保存的是与 标签Label等价的最小Label
    for i in range(2,len(labelSet)):
        curLabel = labelSet[i]
        preLabel = labelSet[curLabel]
        while preLabel != curLabel:
            curLabel = preLabel
            preLabel = labelSet[preLabel]
        labelSet[i] = curLabel

    # 第二遍扫描更新图像
    for row in range(rows):
        for col in range(cols):
            lableImg[row,col] = labelSet[int(lableImg[row,col])]
    # 或者如下
    # lableImg = np.array([labelSet[int(x)] for x in lableImg])

    return lableImg, len(set(labelSet)) - 2 # 因为 0,1 两个不是标签,因此标签数量应该是总标签数量减去2

In [52]:
im = array(Image.open('houses.png').convert('L'))
im = cv2.threshold(im,128,1,type=cv2.THRESH_BINARY_INV)[1]
lableImg, labelNum = icvprCcaByTwoPass(im)
print(labelNum)
imshow(lableImg)


45
Out[52]:
<matplotlib.image.AxesImage at 0x3128d828>

Seed-Filling(种子填充法)

(1)扫描图像,直到当前像素点B(x,y) == 1:

  a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;

  b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;

  c、重复b步骤,直到栈为空;

此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;

(2)重复第(1)步,直到扫描结束;

扫描结束后,就可以得到图像B中所有的连通区域;

Seed-Filling C++ 版本
//  Connected Component Analysis/Labeling By Seed-Filling Algorithm 
//  Author:  www.icvpr.com  
//  Blog  :  http://blog.csdn.net/icvpr 
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>


void icvprCcaBySeedFill(const cv::Mat& _binImg, cv::Mat& _lableImg)
{
    // connected component analysis (4-component)
    // use seed filling algorithm
    // 1. begin with a foreground pixel and push its foreground neighbors into a stack;
    // 2. pop the top pixel on the stack and label it with the same label until the stack is empty
    // 
    // foreground pixel: _binImg(x,y) = 1
    // background pixel: _binImg(x,y) = 0


    if (_binImg.empty() ||
        _binImg.type() != CV_8UC1)
    {
        return ;
    }

    _lableImg.release() ;
    _binImg.convertTo(_lableImg, CV_32SC1) ;

    int label = 1 ;  // start by 2

    int rows = _binImg.rows - 1 ;
    int cols = _binImg.cols - 1 ;
    for (int i = 1; i < rows-1; i++)
    {
        int* data= _lableImg.ptr<int>(i) ;
        for (int j = 1; j < cols-1; j++)
        {
            if (data[j] == 1)
            {
                std::stack<std::pair<int,int>> neighborPixels ;   
                neighborPixels.push(std::pair<int,int>(i,j)) ;     // pixel position: <i,j>
                ++label ;  // begin with a new label
                while (!neighborPixels.empty())
                {
                    // get the top pixel on the stack and label it with the same label
                    std::pair<int,int> curPixel = neighborPixels.top() ;
                    int curX = curPixel.first ;
                    int curY = curPixel.second ;
                    _lableImg.at<int>(curX, curY) = label ;

                    // pop the top pixel
                    neighborPixels.pop() ;

                    // push the 4-neighbors (foreground pixels)
                    if (_lableImg.at<int>(curX, curY-1) == 1)
                    {// left pixel
                        neighborPixels.push(std::pair<int,int>(curX, curY-1)) ;
                    }
                    if (_lableImg.at<int>(curX, curY+1) == 1)
                    {// right pixel
                        neighborPixels.push(std::pair<int,int>(curX, curY+1)) ;
                    }
                    if (_lableImg.at<int>(curX-1, curY) == 1)
                    {// up pixel
                        neighborPixels.push(std::pair<int,int>(curX-1, curY)) ;
                    }
                    if (_lableImg.at<int>(curX+1, curY) == 1)
                    {// down pixel
                        neighborPixels.push(std::pair<int,int>(curX+1, curY)) ;
                    }
                }       
            }
        }
    }
}
Seed-Filling Python 版本

In [53]:
def icvprCcaBySeedFill(img):
    """
    使用如下算法:
        1. begin with a foreground pixel and push its foreground neighbors into a stack;
        2. pop the top pixel on the stack and label it with the same label until the stack is empty
        
    foreground pixel: img(x,y) = 1
    background pixel: img(x,y) = 0
    """
    lableImg = np.array(im,dtype=np.float32) # 因为标签的值可能超过 255
    label = 1 # 标签从 2 开始
    rows, cols = img.shape
    for row in range(1,rows-1):
        data = lableImg[row,:]
        for col in range(1,cols-1):
            if data[col] == 1:
                neighborPixels = []
                neighborPixels.append((row,col))  # 起始像素位置
                label += 1  # 开始一个新的标签
                while not neighborPixels == []:
                    # 获取堆栈顶层的像素位置并将其对应的像素标记为同一个 label
                    curPixel = neighborPixels[-1]
                    curRow, curCol = curPixel
                    lableImg[curPixel] = label
                    # 弹出堆栈顶层的像素位置
                    neighborPixels.pop()
                    # 将前景(像素值为1)中的 4-邻接 像素的位置压入堆栈
                    neighbors_4 = []
                    if curCol-1 > -1:
                        neighbors_4.append((curRow,curCol-1)) # 左邻接像素
                    if curCol+1 < cols:
                        neighbors_4.append((curRow,curCol+1)) # 右邻接像素
                    if curRow-1 > -1:
                        neighbors_4.append((curRow-1,curCol)) # 上邻接像素
                    if curRow+1 < rows:
                        neighbors_4.append((curRow+1,curCol)) # 下邻接像素
                    for pos in neighbors_4:
                        if lableImg[pos] == 1:
                             neighborPixels.append(pos)
    return lableImg, label-1 # 因为 1 不是标签所以标签数量应该是标签值减去1

In [54]:
im = array(Image.open('houses.png').convert('L'))
im = cv2.threshold(im,128,1,type=cv2.THRESH_BINARY_INV)[1]
lableImg, labelNum = icvprCcaBySeedFill(im)
print(labelNum)
imshow(lableImg)


45
Out[54]:
<matplotlib.image.AxesImage at 0x31518710>

ROF 图像去噪



In [55]:
def denoise(im,U_init,tolerance=0.1,tau=0.125,tv_weight=100):
    """ An implementation of the Rudin-Osher-Fatemi (ROF) denoising model
        using the numerical procedure presented in Eq. (11) of A. Chambolle
        (2005). Implemented using periodic boundary conditions.
        
        Input: noisy input image (grayscale,灰度), initial guess for U, weight of 
        the TV-regularizing term(正则项权值), steplength, tolerance for the stop criterion
        
        Output: denoised(去噪) and detextured image, texture residual(残留). """
        
    m,n = im.shape # 噪声图像大小

    # 初始化
    U = U_init
    Px = zeros((m, n)) # 对偶域的x分量
    Py = zeros((m, n)) # 对偶域的y分量
    error = 1 
    
    while (error > tolerance):
        Uold = U
        
        # 原始变量的梯度
        GradUx = roll(U,-1,axis=1)-U # 变量U梯度的 x 分量
        GradUy = roll(U,-1,axis=0)-U # 变量U梯度的 y 分量
        
        # 更新对偶变量
        PxNew = Px + (tau/tv_weight)*GradUx # non-normalized update of x-component (dual 对偶域)
        PyNew = Py + (tau/tv_weight)*GradUy # non-normalized update of y-component (dual)
        NormNew = maximum(1,sqrt(PxNew**2+PyNew**2))
        
        Px = PxNew/NormNew # update of x-component (dual)
        Py = PyNew/NormNew # update of y-component (dual)
        
        # update the primal variable(原始变量)
        RxPx = roll(Px,1,axis=1) # right x-translation of x-component
        RyPy = roll(Py,1,axis=0) # right y-translation of y-component
        
        DivP = (Px-RxPx)+(Py-RyPy) # divergence(散度) of the dual field.
        U = im + tv_weight*DivP # update of the primal variable
        
        # update of error
        error = linalg.norm(U-Uold)/sqrt(n*m);
        
    return U,im-U # denoised image and texture residual

In [56]:
from numpy import random

# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# create synthetic image with noise
im = zeros((500,500))
im[100:400,100:400] = 128
im[200:300,200:300] = 255
im = im + 30*random.standard_normal((500,500))

U,T = denoise(im,im)
G = filters.gaussian_filter(im,10)


# save the result
#imsave('synth_original.pdf',im)
#imsave('synth_rof.pdf',U)
#imsave('synth_gaussian.pdf',G)


# plot
figure()
gray()

subplot(1,3,1)
imshow(im)
#axis('equal')
axis('off')
title(u'原噪声图像', fontproperties=font)

subplot(1,3,2)
imshow(G)
#axis('equal')
axis('off')
title(u'高斯模糊后的图像', fontproperties=font)

subplot(1,3,3)
imshow(U)
#axis('equal')
axis('off')
title(u'ROF降噪后的图像', fontproperties=font)


Out[56]:
<matplotlib.text.Text at 0x318c1c88>

In [ ]: