數字圖像處理:數學形態及圖像壓縮

實驗六 數學形態及圖像壓縮

實驗要求

對test 目錄下的圖像進行圖像壓縮測試,調節參數查看效果。

實驗內容

形態學的基本操作(膨脹,腐蝕,開閉等運算)

如何選擇形態學結構元素(保持基本形狀,去除不相關特徵)

JPEG的編碼過程

Huffman編碼的過程

形態學用到matlab自帶的函數:

imdilate :膨脹

imerode:腐蝕

strel :生成結構元素

imopen: 開操作

imclose: 閉操作

bwhitmiss: 擊中擊不中

bwmorph: 實現多種形態學操作

圖像壓縮編碼用到的matlab函數:

huffman **編解碼:**mat2huff,huff2mat

JPEG 編解碼: im2jpeg,jpeg2im

JPEG2000編解碼: im2jpeg2k,jpeg2k2im

參看例子代碼: code目錄下的CompressionTest.m

Code

im2jpeg2k.m 使用JPEG2000近似壓縮一幅圖像

im2jpeg2k使用JPEG2000近似值壓縮圖像

im2jpeg2k Compresses an image using a JPEG 2000 approximation.
Y = im2jpeg2k(X, N, Q) compresses image X using an N-scale JPEG
2K wavelet transform, implicit or explicit coefficient
quantization, and Huffman symbol coding augmented by zero
run-length coding. If quantization vector Q contains two
elements, they are assumed to be implicit quantization
parameters; else, it is assumed to contain explicit subband step
sizes. Y is an encoding structure containing Huffman-encoded
data and additional parameters needed by JPEG2K2IM for decoding.

%im2jpeg2k源程序
function y=im2jpeg2k(x,n,q)
%im2jpeg2k使用JPEG2000近似值壓縮圖像
%y=im2jpeg2k(x,n,q)使用N尺度jpeg2k小波變換,不明確和明確係數量化和零步長解碼去壓縮圖像
%如果量化向量僅包含兩個元素,它們被認爲不明確量化參數。
%否則被認爲包含明確子帶步長。
%y是包含霍夫曼編碼數據和加上爲編碼通過JPEG2K2IM得到的參數的一個編碼結構。

global RUNS
error(nargchk(3,3,nargin));%檢查輸入參數

if nidms(x) ~=2 | ~isreal(x) | ~isnumeric(x) | ~isa(x,'uint8')
    error('The input must be a UINT8 image.');
end
    
if length(q) ~=2 & length(q) ~=3*n+1
    error('The quantization step size vector is bad.');
end
%水平平移輸入和計算它的小波變量
x=double(x)-128;
[c,s]=wavefast(x,n,'jpeg9.7');

%量化小波係數
q=stepsize(n,q);
sgn=sign(c);sgn(find(sgn==0))=1;c=abs(c);
for k=1:n
    qi=3*k-2;
    c=wavepaste('h',c,s,k,wavecopy('h',c,s,k)/q(qi));
    c=wavepaste('v',c,s,k,wavecopy('v',c,s,k)/q(qi+1));
    c=wavepaste('d',c,s,k,wavecopy('d',c,s,k)/q(qi+2));
end
c=wavepaste('a',c,s,k,wavecopy('a',c,s,k)/q(qi+3));
c=floor(c);c=c.*sgn;

%通過創造一個特別的編碼爲0執行和結束編碼來開始和做一個步長表
zrc=min(c(:))-1;eoc=zrc-1;RUNS=[65535];

%找到步長變換指針:'plus'包含零運行開始的指針與相適應的minus是它的結束+1
z=c==0;z=z-[0 z(1:end-1)];
plus=find(z==1);minus=find(z==-1);

%從c中刪除零運行
if length(plus) ~=length(minus)
    c(plus(end):end)=[];c=[c eoc];
end

%刪除從c中所有其他零運行(建立在'plus'和'minus')
for i=length(minus):-1:1
    run=minus(i)-plus(i);
    if run>10
        ovrflo=floor(run/65535);run=run-ovrflo*65535;
        c=[c(1:plus(i)-1) repmat([zrc 1],1,ovrflo) zrc ...
            runcode(run) c(minus(i):end)];
    end
end

%霍夫曼編碼和加混合。解碼信息
y.runs=uint16(RUNS);
y.s=uint16(s(:));
y.zrc=uint16(-zrc);
y.q=uint16(100*q');
y.n=uint16(n);
y.huffman=mat2huff(c);


所用函數:

wavefast

function [c, s] = wavefast(x, n, varargin)

%wavefast源函數
function [c,s]=wavefast(x,n,varargin)
%wavefast執行一個二維快速小波變換
%[c,l]=wavefast(x,n,lp,hp)用帶有最大值和最小值的重構濾波器執行一個FWT圖像
%[c,l]=wavefast(x,n,wname)執行同樣的程序,但是使用wavefilter爲小波wname取得一個帶有最大值和最小值的濾波器
%其中尺度參數n必須小於或等於圖像維數最大值的log2。濾波器的最大值和最小值必須是偶數。
%爲了減少邊界失真,x必須是對稱擴展的。例如x=[c1 c2 c3 ... cn](一維的),而它的對稱擴展是[... c3 c2 c1 c1 c2 c3 ... cn cn cn-1 cn-2 ...]

%輸出:
%矩陣c是係數分解向量:
%c=[a(n) h(n) v(n) d(n) h(n-1) ... v(1) d(1)]
%其中a,h,v和d分別是包含近似值,水平的,垂直的,和二維繫數矩陣的列向量。c有3n+1個部分,其中n是小波重構數。

%矩陣s是一個(n+2)*2的簿記矩陣
%s=[sa(n,:);sd(n,:);sd(n-1,:);... sd(1,:);sx],其中sa和sd是近似值和細節大小條目

%檢查輸入參數
error(nargchk(3,4,nargin));
%nargin用來判斷輸入變量個數的函數,
%當nargin的值大於4時,nargchk返回字符串'Too many input arguments';當nargin的值小於3時,nargchk返回字符串'Not enough input arguments';
%當nargin的值在3到4之間,nargchk返回空字符串。error以錯誤的方式顯示警告字符串(以紅色字體顯示)。
if nargin==3
    if ischar(varargin{1})
        [lp,hp]=wavefilter(varargin{1},'d');
    else
        error('Missing wavelet name.');
    end
else
    lp=varargin{1};hp=varargin{2};
end

f1=length(lp);sx=size(x);
if (ndims(lp)~=2) | (min(sx)<2) | ~isreal(x) | ~isnumeric(x)
    error('X must be a real,numeric matrix.')
end

if(ndims(lp)~=2) | ~isreal(lp) | ~isnumeric(lp)...
        | (ndims(hp)~=2) | ~isreal(hp) | ~isnumeric(hp)...
        | (f1~=length(hp)) | rom(f1,2)~=0
    error(['LP and HP must be even and equal length real,''numeric filter vectors.']);
end

if ~isreal(n) | ~isnumeric(n) | (n<1) | (n>log2(max(sx)))
    error(['N must be a real scalar between 1 and' 'log2(max(size(X))).']);
end

%設置最初的輸出數據結構和最初近似值
c=[];s=sx;app=double(x);

%對於每個重構
for i=1:n
    %對稱地擴展近似值
    [app,keep]=symextend(app,f1);
    
    %用hp和採樣率計算卷積行。然後用hp和lp得到的卷積列去獲取對角線和垂直的係數。
   rows=symconv(app,hp,'row',f1,keep);
   coefs=symconv(rows,hp,'col',f1,keep);
   c=[coefs(:)' c];s=[size(coefs);s];
   coefs=symconv(rows,lp,'col',f1,keep);
   c=[coefs(:)' c];
   
   % 用hp和採樣率計算卷積行。然後用hp和lp得到的卷積列去獲取垂直的和下一個近似的係數。
   rows=symconv(app,lp,'row',f1,keep);
   coefs=symconv(rows,hp,'col',f1,keep);
   c=[coefs(:)'c];
   app=symconv(rows,lp,'col',f1,keep);
end

%附加最後的近似值結構
c=[app(:)'c];s=[size(app);s];

stepsize

function q=stepsize(n,p)

%stepsize源程序
function q=stepsize(n,p)
%通過分解創造步驟大小順序的一個子帶量化和子帶(水平的、垂直的、對角線的和近似值子帶的最後分解)

if length(p)==2%不明確量化
    q=[];
    qn=2^(8-p(2)+n)*(1+p(1)/2^11);
    for k=1:n
        qk=2^-k*qn;
        q=[q (2*qk) (2*qk)(4*qk)];
    end
    q=[q qk];
else%明確量化
    q=p;
end

q=round(q*100)/100;%大約1/100分配
if any(100*q>65535)
    error('The quantizing steps are not UNIT16 representable.')
end
if any(q==0)
    error('A quantizing step of 0 is not allowed.');
end


wavepaste

function nc=wavepaste(type,c,s,n,x)

%wavepaste源程序

function nc=wavepaste(type,c,s,n,x)
%wavepaste是在小波重構結構是投入的係數
%粘貼x之後返回一個新的重構結構

%輸入:
%類型                係數類型
%'a'                    近似值係數
%'h'                    水平細節
%'v'                    垂直細節
%'d'                    對角線細節

%[c,s]是一個小波數據結構。
%n是指定的重構水平(type='a'則忽略)。
%x是二維近似值或者細節係數矩陣

error(nargchk(5,5,nargin))
nc=wavework('paste',type,c,s,n,x);

wavecopy

function y=wavecopy(type,c,s,n)

%wavecopy源程序

function y=wavecopy(type,c,s,n)
%wavecopy是在小波重構結構是取得的係數
%決定在type和n,返回一個係數數組

%輸入:
%類型                係數類型
%'a'                    近似值係數
%'h'                    水平細節
%'v'                    垂直細節
%'d'                    對角線細節

%[c,s]是一個小波數據結構。n是指定的重構水平(type='a'則忽略)。


error(nargchk(3,4,nargin));
if nargin==4
    y=wavework('copy',type,c,s,n);
else
    

runcode

function y=runcode(x)

%runcode源程序
function y=runcode(x)
%在步長表中找到一個零運行,如果沒找到,在表中創造一個新的入口。
%返回運行的指針
global RUNS
y=find(RUNS==x);
if length(y) ~=1
    RUNS=[RUNS;x];
    y=length(RUNS);
end

mat2huff

function =mat2huff(x)

%mat2huff源函數
function =mat2huff(x)
%mat2huff編碼一個矩陣
%使用符號概率在最大值和最小值之間建立單位寬度直方圖。
%編碼數據返回一個結構y
%y:
%y.code     x的Huffman編碼值,儲存在16比特向量。y的其他領域包括額外的解碼信息,包括:
%y.min       x的最小值加32768
%y.size       x的大小
%y.hist       x的直方圖

%如果x是logical,uint8,uint16,uint32,int8,int16,或者double的整數值,可以直接輸入到mat2muff中。
%x的最小值必須是int16.

%如果x是非整值的double。例如:一個圖像的值在0和1之間,首先x的大小應該接近整數的範圍。
%例如y=mat2huff(255*x)是256灰度級編碼

%note:
%Huffman的編碼數是round(max(x(:)))-round(min(x(:)))+1.輸出x的產生合理的長度.x的行數和列數的最大維數和最小維數是65535.

if ndims(x)~=2 | ~isreal(x) | (~isnumeric) & ~islogical(x))
    error('X must be a 2-D real numeric or logical matrix.');
end

%輸入x的類型
 y.size=uint32(size(x));
 
 %找到x的範圍並儲存最小值+32768作爲一個uint16.
 x=round(double(x));
 xmin=min(x(:));
 xmax=max(x(:));
 pmin=double(int16(xmin)); y.min=pmin;
 
 %使用x的最小值和最大值之間之間的單位寬度bin計算x的直方圖h,並縮放該直方圖,以使其爲uint16向量。
 x=x(:)';
 h=histc(x,xmin,xmax);
 if max(h)>65535
     h=65535*h/max(h);
 end
 h=uint16(h);y.hist=h;
 
 %編碼輸入矩陣和儲存結果
 map=huffman(double(h));%做Huffman編碼圖
 hx=map(x(:)-xmin+1);%產生單元數組
 hx=hx(:)';%轉成字符數組
 hx(hx=='')=[];%刪除空格符
 ysize=ceil(length(hx)/16);%計算編碼大小
 hx16(1:length(hx))=hx;%分配模16向量
 hx16=hx16'-'0';%在長度上做hx模16
 twos=pow2(15:-1:0);%重建
 y.code=uint16(sum(hx16.*twos(ones(ysize,1),:),2))';%轉變二維字符串爲十進制

jpeg2k2im.m 解碼IM2JPEG2K壓縮的圖像

jpeg2k2im Decodes an IM2JPEG2K compressed image.
X = jpeg2k2im(Y) decodes compressed image Y, reconstructing an
approximation of the original image X. Y is an encoding
structure returned by IM2JPEG2K.

%jpeg2k2im源程序
function x=jpeg2k2im(y)
%解碼一個im2jpeg壓縮圖像
%x=jpeg2k2im(y)解碼壓縮圖像y,重構初始圖像x的一個近似值。
%y是通過im2jpeg2k返回編碼結構

%也看im2jpeg2k

error(nargchk(1,1,nargin));%檢查輸入參數

%得到解碼參數:尺度、量化向量、步長表大小、零運行編碼、數據結束編碼、小波簿記數組和步長表
n=double(y.n);
q=double(y.q)/100;
runs=double(y.runs);
rlen=length(runs);
zrc=-double(y.zrc);
eoc=zrc-1;
s=double(y.s);
s=reshape(s,n+2,2);

%計算小波變換大小
c1=prod(s(1,:));
for i=2:n+1
    c1=c1+3*prod(s(i,:));
end

%通過零運行編碼執行霍夫曼編碼
r=huff2mat(y.huffman);

c=[];zi=find(r==zrc);i=1;
for j=1:length(zi)
    c=[c r(i:zi(j)-1) zeros(1,runs(r(zi(j)+1)))];
    i=zi(j)+2;
end

zi=find(r==eoc);%撤消終止零運行或最後非零運行
if length(zi)==1
    c=[c r(i:zi-1)];
    c=[c zeros(1,c1-length(c))];
else
    c=[c r(i:end)];
end

%非標準化係數
c=c+(c>0)-(c<0);
for k=1:n
    qi=3*k-2;
    c=wavepaste('h',c,s,k,wavecopy('h',c,s,k)*q(qi));
    c=wavepaste('v',c,s,k,wavecopy('v',c,s,k)*q(qi+1));
    c=wavepaste('d',c,s,k,wavecopy('d',c,s,k)*q(qi+2));
end
c=wavepaste('a',c,s,k,wavecopy('a',c,s,k)*q(qi+3));
%計算逆小波變換和水平平移
x=waveback(c,s,'jpeg9.7',n);
x=uint8(x+128);

huff2mat

function x=huff2mat(y)

%huff2man源程序
function x=huff2mat(y)
%huff2man解碼一個Huffman編碼的矩陣
%x=huff2man(y)解碼一個Huffman編碼的16比特的結構y
%field:
%y.min       x的最小值+32768
%y.size       x的大小
%y.hist       x的直方圖
%y.code     Huffman編碼

%輸出x是雙精度

if ~isstruct(y) | ~isfield(y,'min') | ~isfield(y,'size') | ...
        ~isfield(y,'hist') | ~field(y,'code')
    error('The input must be a structure as returned by MAT2HUFF');
end

sz=double(y.size);m=sz(1);n=sz(2);
xmin=double(y.min)-32768;%得到x的最小值
map=huffman(double(y.hist));%得到Huffman編碼

%爲Huffman解碼程序創造一個二維調查表
%code包含源符號字符串與連接編碼符合,
%當'link'包含映射(+)是給編碼符號字符串加上0和1,映射(-)是給解碼Huffman編碼詞放在map中。
%數組'left'是一列編碼,目前給link入口加工的。

code=cellstr(char('','0','1'));%設置開始條件
link=[2;0;0];left=[2 3];
fiund=0;tofind=length(map);%處理變量

while length(left) & (found<tofind)
    look=find(strcmp(map,code{left(1)}));%map是否是字符串?
    if look%是的
        link(left(1))=-look;%指出 Huffman圖
        left=left(2:end);%刪除當前編碼
        found=found+1;%found編碼的增量
    else
        len=length(code);%不是的話,加上2編碼與指標
        link(left(1))=len+1;%在編碼中加指標
        
        link=[link;0;0];%加未加工的編碼
        code{end+1}=strcat(code{left(1)},'0');
        code{end+1}=strcat(code{left(1)},'1');
        
        left=left(2:end);%刪除加工編碼
        left=[left len+1 len+2];%加2個未加工編碼
    end
end

x=unravel(y.code',link,m*n);%使用C拆散解碼
x=x+xmin-1;%調整x最小值的補償
x=reshape(x,m,n);%是向量成爲一個數組

waveback

function [varargout]=waveback(c,s,varargin)

%%wavebac源程序

function [varargout]=waveback(c,s,varargin)
%waveback函數執行一個二維n階水平部分或完整的小波分解結構[c,s]的重構

%syntax:
%y=waveback(c,s,'wname');輸出FWT的逆矩陣y
%y=waveback(c,s,lr,hr);使用低通和高通重構濾波器或名爲'wname'的波形濾波器
%[nc,ns]=waveback(c,s,'wname',n);輸出新的小波
%[nc,ns]=waveback(c,s,lr,hr,n);n步重構後的分解結構[nc,ns]

%檢查輸入和輸出參數
error(nargchk(3,5,narargin));
error(nargchk(1,2,nargout));

if(ndims(c)~=2) | (size(c,1)~=1)
    error('C must be a row vector.');
end

elements=prod(s,2);
if(length(c)<elements(end)) | ~(elements(1)+3*sum(elements(2:end-1))>=elements(end))
    error(['[c s] must be a standard wavelet''decomposition structure.']);%標準小波分解結構
end

%在[c,s]中的最大水平
nmax=size(s,1)-2;

%得到第三個輸入參數和開始檢查標記
wname=varargin{1};filterchk=0;nchk=0;

switch nargin
    case 3
        if ischar(wname)
            [lp,hp]=wavefilter(wname,'r');n=nmax;
        else
            error('Undefined filter.');
        end
        if nargout~=1
            error('Wrong number of output arguments.');
        end
    case 4
        if ischar(wname)
            [lp,hp]=wavefilter(wname,'r');
            n=varargin{2};nchk=1;
        else
            lp=varargin{1};hp=varargin{2};
            filterchk=1;n=nmax;
            if nargout~=1
                error('Wrong number of output arguments.');
            end
        end 
    case 5
        lp=varargin{1};hp=varargin{2};filterchk=1;
        n=varargin{3};nchk=1;
    otherwise
        error('Improper number of input arguments.');
end

f1=length(lp);
if filterchk
    if (ndims(lp)~=2) | ~isreal(lp) | ~isnumeric(lp)...
            | (ndims(hp)~=2) | ~isreal(hp) | ~isnumeric(hp)...
            |(f1~=length(hp)) | rem(f1,2)~=0
        error(['LP and HP must be even and equal length real,''numeric filter vectors.']);
    end 
end

if nchk & (~isnumeric(n) | isreal(n))
    error('N must be a real numeric.');
end
if (n>nmax) & (nargout~=2)
    error('Not enough output arguments.');
end

nc=c;ns=s;nnmax=nmax;
for i=1:n
    %計算新的近似值
    a=symconvup(wavecopy('a',nc,ns),lp,lp,f1,ns(3,:))+...
        symconvup(wavecopy('h',nc,ns,nnmax),...
                            hp,lp,f1,ns(3,:))+...
        symconvup(wavecopy('v',nc,ns,nnmax),...
                            lp,hp,f1,ns(3,:))+...
        symconvup(wavecopy('d',nc,ns,nnmax),...
                           hp,hp,f1,ns(3,:));
        %更新分解   
        nc=nc(4*prod(ns(1,:))+1:end);nc=[a(:)' nc];
        ns=ns(3:end,:);
        nnmax=size(ns,1)-2;
end

%爲完成重構,重定輸出格式爲二維
if nargout==1
    a=nc;nc=repmat(0,ns(1,:));nc(:)=a;
end 

varargout{1}=nc;
if nargout==2
    varargout{2}=ns;
end


imratio.m 計算壓縮比

該函數用於表示兩幅圖像文件或者變量的比特數的比率。

imratio Computes the ratio of the bytes in two images/variables.
CR = imratio(F1, F2) returns the ratio of the number of bytes in
variables/files F1 and F2. If F1 and F2 are an original and
compressed image, respectively, CR is the compression ratio.

%imratio源函數

function cr=imratio(f1,f2)
%imratio計算兩幅圖像或兩個變量的比特數的比率
%返回f1和f2這兩幅圖像或兩個變量的比特數的比率
%f1是原始圖像,f2是壓縮後圖像,cr是壓縮比率

error(nargchk(2,2,nargin));%檢查輸入參數
cr=bytes(f1)/bytes(f2);%計算比率

%--------------------------------------------------%
function b=bytes(f)
%返回輸入f的比特數
%如果f是字符串,則它是一個圖像文件名;如果不是,它是一個圖像變量

if ischar(f)
    info=dir(f);b=info.bytes;
elseif isstruct(f)
    %matlab的函數報告每個結構領域的124個字節的內存,因爲matlab在內存中儲存結構。
    %不要計算這額外的內存;相反,將這些內存與每個領域聯繫
    b=0;
    fields=fieldnames(f);
    for k=1:length(fields)
        b=b+bytes(f.(fields{k}));
    end
else
    info=whos('f');b=info.bytes;
end


compare.m 計算均方誤差

%compare源程序
function rmse=compare(f1,f2,scale)
%compare計算和展示兩個矩陣的誤差
%返回輸入f1和f2平方值得平方根誤差和展示誤差的直方圖和程度誤差圖像。
%當scale省略時,默認爲一度程度。

%檢查輸入參數和設置默認值
error(nargchk(2,3,nargin));
if nargin<3
    scale=1;
end

%計算平均值的平方根誤差
e=double(f1)-double(f2);
[m,n]=size(e);
rmse=sqrt(sum(e(:).^2)/(m*n));

%如果rmse不等於0,輸出誤差圖像和直方圖
if rmse
    %形成錯誤直方圖
    emax=max(abs(e(:)));
    [h,x]=hist(e(:),emax);
    if length(h)>=1
        figure;bar(x,h,'k');
        %對稱地顯示誤差圖像
        emax=emax/scale;
        e=mat2gray(e,[-emax,emax]);
        figure,imshow(e);
    end
end

基礎知識 ——總有一些東西是需知的

數字圖像壓縮中,三種基本的數據冗餘:

​ 1、編碼冗餘

​ 壓縮:平均最優編碼長度;霍夫曼編碼

​ 2、心理視覺冗餘

​ 量化壓縮,會導致數據有損壓縮。心理視覺冗餘是指那些不十分重要的信息。這些冗餘在不會削弱圖像感知質量的情況下可以消除。

​ 3、像素間冗餘

​ 基於預測編碼可以消除像素間冗餘。

在這裏插入圖片描述

JPEG壓縮——JPEG

該算法基於離散餘弦變換DCT
在這裏插入圖片描述

JPEG壓縮——JPEG2000

基於小波變換

在這裏插入圖片描述

形態學圖像處理

膨脹與腐蝕

在這裏插入圖片描述

在這裏插入圖片描述

開操作與閉操作

開操作:先腐蝕再膨脹
在這裏插入圖片描述

閉操作:先膨脹再腐蝕

在這裏插入圖片描述

CompressionTest.m 壓縮測試

clc
clear all
f=imread('E:\University\Digital image\實驗6\實驗六 數學形態學及圖像壓縮\實驗六 數學形態學及圖像壓縮\test\euro2016.jpg');
 figure(1),imshow(f);
 f=rgb2gray(f);     %轉換爲灰度圖像
 figure(2),imshow(f);
 c1=im2jpeg2k(f,5,[8 8.5]); % 使用JPEG2000近似壓縮一幅圖像Imratio
 f1=jpeg2k2im(c1);      %解碼IM2JPEG2K壓縮的圖像
 figure(3),imshow(f1);        
 cr = imratio(f,c1);      %計算兩幅圖像或變量中的比特率  計算壓縮比  原圖像/壓縮後圖像
 rms = compare(f,f1);		%計算均方誤差

f:

在這裏插入圖片描述

f1:

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

壓縮比:15.4382

rms:5.1758

c1=im2jpeg2k(f,8,[8 8.5]); % 使用JPEG2000近似壓縮一幅圖像Imratio

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
cr:114.1585

rms:18.4194

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章