【建議收藏】16個OpenCV函數開始你的計算機視覺之旅

計算機視覺是當前行業中比較熱門的領域之一。由於技術和研究的飛速發展,它正在蓬勃發展。但這對於新來者來說仍是個艱鉅的任務。XR開發者或數據科學家在過渡到計算機視覺時面臨着一些常見的挑戰,包括:

1.我們如何清理圖像數據集?圖像有不同的形狀和大小

2.數據獲取中一直存在的問題。在建立計算機視覺模型之前,我們應該收集更多圖像嗎?

3.學習深度學習對建立計算機視覺模型是否必不可少?我們可以不使用機器學習技術嗎?

4.我們可以在自己的機器上建立計算機vsiion模型嗎?並非每個人都可以使用GPU和TPU!

以下內容由公衆號:AIRX社區(國內領先的AI、AR、VR技術學習與交流平臺) 整理

目錄

  1. 什麼是計算機視覺?

  2. 爲什麼要將OpenCV用於計算機視覺任務?

  3. 讀取,寫入和顯示圖像

  4. 改變色彩空間

  5. 調整圖像大小

  6. 影像旋轉

  7. 圖片翻譯

  8. 簡單圖像閾值

  9. 自適應閾值

  10. 圖像分割(分水嶺算法)

  11. 按位運算

  12. 邊緣檢測

  13. 圖像過濾

  14. 影像輪廓

  15. 尺度不變特徵變換(SIFT)

  16. 加速的強大功能(SURF)

  17. 特徵匹配

  18. 人臉檢測

什麼是CV

在進入OpenCV之前,讓我快速解釋一下什麼是計算機視覺。對本文其餘部分將要討論的內容有一個直觀的瞭解。人類能夠自然地看到和感知世界。通過視覺和感知的天賦從周圍環境收集信息是我們的第二天性。

快速瀏覽上圖。我們花了不到一秒鐘的時間發現其中有一隻貓,一條狗和一條人的腿。對於機器,這種學習過程變得很複雜。解析圖像和檢測對象的過程涉及多個複雜步驟,包括特徵提取(邊緣檢測,形狀等),特徵分類等。

計算機視覺是當前行業中最熱門的領域之一。可以預計,未來2-4年將會有大量的職位空缺。那麼問題是你準備好利用這些機會了嗎?請花點時間考慮一下–當你想到計算機視覺時,會想到哪些應用程序或產品?我們每天都使用其中一些,使用面部識別功能解鎖手機,智能手機攝像頭,自動駕駛汽車等功能,無處不在。

關於OpenCV

OpenCV庫最初是英特爾的一項研究項目。就其擁有的功能而言,它是目前最大的計算機視覺庫。OpenCV包含2500多種算法的實現!它可免費用於商業和學術目的。該庫具有適用於多種語言的接口,包括Python,Java和C ++。

OpenCV的第一個版本1.0於2006年發佈,此後OpenCV社區發展迅猛。

現在,讓我們將注意力轉移到本文背後的思想上-OpenCV提供的衆多功能!我們將從數據科學家的角度研究OpenCV,並瞭解一些使開發和理解計算機視覺模型的任務變得更加容易的功能。

讀取,寫入和顯示圖像

機器使用數字來查看和處理一切,包括圖像和文本。你如何把圖像轉換成數字?沒錯,像素!

每個數字表示該特定位置的像素強度。在上面的圖像中,我展示了灰度圖像的像素值,其中每個像素只包含一個值,即該位置的黑色強度。

注意,彩色圖像對於單個像素有多個值。這些值表示各自通道的強度——例如,RGB圖像的紅色、綠色和藍色通道。

讀和寫圖像是必不可少的任何計算機視覺項目。OpenCV庫使這個函數變得非常簡單。

 

 

#import the librariesimport numpy as npimport matplotlib.pyplot as pltimport cv2%matplotlib inline#reading the image image = cv2.imread('index.png')image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)#plotting the imageplt.imshow(image)#saving imagecv2.imwrite('test_write.jpg',image)

默認情況下,imread函數以BGR(藍綠色紅色)格式讀取圖像。我們可以使用imread函數中的額外標誌來讀取不同格式的圖像:

  • cv2.IMREAD_COLOR:  加載彩色圖像的默認標誌

  • cv2.IMREAD_GRAYSCALE:  以灰度格式加載圖像

  • cv2.IMREAD_UNCHANGED:  以給定格式(包括Alpha通道)加載圖像。Alpha通道存儲透明度信息,Alpha通道的值越高,像素越不透明。

改變色彩空間

顏色空間是一種協議,用於以一種使顏色易於複製的方式表示顏色。我們知道灰度圖像有單個像素值,而彩色圖像每個像素包含3個值——紅、綠、藍通道的強度。

大多數計算機視覺用例處理RGB格式的圖像。然而,像視頻壓縮和設備獨立存儲這樣的應用程序嚴重依賴於其他顏色空間,比如顏色-飽和度-值或HSV顏色空間。

正如你所理解的,RGB圖像是由不同顏色通道的顏色強度組成的,即強度和顏色信息混合在RGB顏色空間中,而在HSV顏色空間中,顏色和強度信息是相互分離的。這使得HSV顏色空間對光線變化更加健壯。OpenCV默認以BGR格式讀取給定的圖像。因此,在使用OpenCV讀取圖像時,需要將圖像的顏色空間從BGR更改爲RGB。讓我們看看怎麼做:

#import the required libraries import numpy as np import matplotlib.pyplot as plt import cv2 %matplotlib inline image = cv2.imread('index.jpg') #converting image to Gray scale gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#plotting the grayscale imageplt.imshow(gray_image) #converting image to HSV formathsv_image = cv2.cvtColor(image,cv2.COLOR_BGR2HSV)#plotting the HSV imageplt.imshow(hsv_image)

調整圖像大小

機器學習模型在固定大小的輸入下工作。同樣的想法也適用於計算機視覺模型。我們用於訓練模型的圖像必須具有相同的尺寸。

現在,如果我們通過從各種來源抓取圖像來創建自己的數據集,這可能會成爲問題。這就是調整圖像大小的功能。

使用OpenCV可以輕鬆地放大和縮小圖像。當我們需要將圖像轉換爲模型的輸入形狀時,此操作對於訓練深度學習模型很有用。OpenCV支持不同的插值和下采樣方法,這些參數可以由以下參數使用:

1.INTER_NEAREST:  最近鄰居插值

2.INTER_LINEAR:  雙線性插值

3.INTER_AREA:  使用像素面積關係進行重採樣

4.INTER_CUBIC:  在4×4像素鄰域上的雙三次插值

5.INTER_LANCZOS4:在8×8鄰域內進行Lanczos插值

OpenCV的調整大小功能默認情況下使用雙線性插值。

import cv2 import numpy as np import matplotlib.pyplot as plt %matplotlib inline #reading the image image = cv2.imread('index.jpg') #converting image to size (100,100,3) smaller_image = cv2.resize(image,(100,100),inerpolation='linear') #plot the resized imageplt.imshow(smaller_image)

影像旋轉

“你需要大量數據來訓練深度學習模型”。大多數深度學習算法在很大程度上取決於數據的質量和數量。但是,如果你沒有足夠大的數據集怎麼辦?並非所有人都能負擔得起手動收集和標記圖像的費用。

假設我們正在建立一個圖像分類模型來識別圖像中存在的動物。因此,下面顯示的兩個圖像都應歸類爲“狗”:

但是,如果沒有對第二幅圖像進行訓練,該模型可能會發現很難將其歸類爲狗。那麼我們應該怎麼做呢?

讓我來介紹一下數據擴充技術。該方法允許我們生成更多的樣本來訓練我們的深度學習模型。數據擴充利用現有的數據樣本,通過應用旋轉、縮放、平移等圖像操作來生成新的數據樣本。這使我們的模型對輸入的變化具有魯棒性,並導致更好的泛化。

旋轉是最常用和最容易實現的數據擴充技術之一。顧名思義,它包括以任意角度旋轉圖像,併爲其提供與原始圖像相同的標籤。想想你在手機中旋轉圖像以達到一定角度的次數——這基本上就是這個功能的作用。​​​​​​​

#importing the required libraries import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline image = cv2.imread('index.png') rows,cols = image.shape[:2] #(col/2,rows/2) is the center of rotation for the image # M is the cordinates of the center M = cv2.getRotationMatrix2D((cols/2,rows/2),90,1) dst = cv2.warpAffine(image,M,(cols,rows)) plt.imshow(dst)

圖片翻譯

圖像平移是一種幾何變換,它將圖像中每個對象的位置映射到最終輸出圖像中的新位置。平移操作之後,輸入圖像中位置(x,y)處的對象將移動到新位置(X,Y):

X = x + dx

Y = y + dy

在此,dx和dy是沿不同維度的各自平移。圖像平移可用於爲模型增加平移不變性,因爲通過翻譯,我們可以更改圖像中對象的位置,從而使模型具有更多多樣性,從而導致更好的泛化性。

以下面的圖片爲例。即使圖像中沒有完整的鞋子,模型也應該能夠將其歸類爲鞋子。

此轉換功能通常在圖像預處理階段使用。查看下面的代碼,看看它在實際情況下如何工作:​​​​​​​

#importing the required libraries import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline #reading the imageimage = cv2.imread('index.png')#shifting the image 100 pixels in both dimensionsM = np.float32([[1,0,-100],[0,1,-100]]) dst = cv2.warpAffine(image,M,(cols,rows)) plt.imshow(dst)

簡單圖像閾值

閾值化是一種圖像分割方法。它將像素值與閾值進行比較,並相應地進行更新。OpenCV支持閾值的多種變化。可以這樣定義一個簡單的閾值函數:

如果Image(x,y)> threshold,則Image(x,y)= 1

否則,Image(x,y)= 0

閾值只能應用於灰度圖像。圖像閾值化的一個簡單應用就是將圖像分爲前景和背景。​​​​​​​

#importing the required libraries import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline 
#here 0 means that the image is loaded in gray scale formatgray_image = cv2.imread('index.png',0)
ret,thresh_binary = cv2.threshold(gray_image,127,255,cv2.THRESH_BINARY)ret,thresh_binary_inv = cv2.threshold(gray_image,127,255,cv2.THRESH_BINARY_INV)ret,thresh_trunc = cv2.threshold(gray_image,127,255,cv2.THRESH_TRUNC)ret,thresh_tozero = cv2.threshold(gray_image,127,255,cv2.THRESH_TOZERO)ret,thresh_tozero_inv = cv2.threshold(gray_image,127,255,cv2.THRESH_TOZERO_INV)
#DISPLAYING THE DIFFERENT THRESHOLDING STYLESnames = ['Oiriginal Image','BINARY','THRESH_BINARY_INV','THRESH_TRUNC','THRESH_TOZERO','THRESH_TOZERO_INV']images = gray_image,thresh_binary,thresh_binary_inv,thresh_trunc,thresh_tozero,thresh_tozero_inv
for i in range(6):    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')    plt.title(names[i])    plt.xticks([]),plt.yticks([])
plt.show()

自適應閾值

在自適應閾值的情況下,對於圖像的不同部分使用不同的閾值。此功能可爲光照條件變化的圖像提供更好的結果,因此稱爲“自適應”。Otsu的二值化方法爲整個圖像找到最佳閾值。它適用於雙峯圖像(直方圖中具有2個峯的圖像)。​​​​​​​

#import the librariesimport numpy as npimport matplotlib.pyplot as pltimport cv2%matplotlib inline
#ADAPTIVE THRESHOLDINGgray_image = cv2.imread('index.png',0)
ret,thresh_global = cv2.threshold(gray_image,127,255,cv2.THRESH_BINARY)#here 11 is the pixel neighbourhood that is used to calculate the threshold valuethresh_mean = cv2.adaptiveThreshold(gray_image,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,11,2)
thresh_gaussian = cv2.adaptiveThreshold(gray_image,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2)
names = ['Original Image','Global Thresholding','Adaptive Mean Threshold','Adaptive Gaussian Thresholding']images = [gray_image,thresh_global,thresh_mean,thresh_gaussian]
for i in range(4):    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')    plt.title(names[i])    plt.xticks([]),plt.yticks([])
plt.show()

 

圖像分割(分水嶺算法)

圖像分割是將圖像中的每個像素分類爲某個類的任務。例如,將每個像素分類爲前景或背景。圖像分割對於從圖像中提取相關部分非常重要。

分水嶺算法是一種經典的圖像分割算法。它將圖像中的像素值視爲地形。爲了找到對象邊界,它以初始標記作爲輸入。然後,該算法開始從標記中泛洪盆地,直到標記在對象邊界處相遇。

假設我們有多個盆地。現在,如果我們用不同顏色的水填充不同的盆地,那麼不同顏色的交點將爲我們提供對象邊界。​​​​​​​

#importing required librariesimport numpy as npimport cv2import matplotlib.pyplot as plt
#reading the imageimage = cv2.imread('coins.jpg')#converting image to grayscale formatgray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#apply thresholdingret,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)#get a kernelkernel = np.ones((3,3),np.uint8)opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations = 2)#extract the background from imagesure_bg = cv2.dilate(opening,kernel,iterations = 3)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)ret,sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)unknown = cv2.subtract(sure_bg,sure_bg)
ret,markers = cv2.connectedComponents(sure_fg)
markers = markers+1
markers[unknown==255] = 0
markers = cv2.watershed(image,markers)image[markers==-1] = [255,0,0]
plt.imshow(sure_fg)

換位運算

按位運算包括AND,OR,NOT和XOR。你可能在編程課上還記得它們!在計算機視覺中,當我們有一個遮罩圖像並且想要將該遮罩應用於另一個圖像以提取感興趣區域時,這些操作非常有用。​​​​​​​

#import required librariesimport numpy as np import matplotlib.pyplot as plt import cv2 %matplotlib inline #read the imageimage = cv2.imread('coins.jpg')#apply thresholdin ret,mask = cv2.threshold(sure_fg,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) #apply AND operation on image and mask generated by thrresholdingfinal = cv2.bitwise_and(image,image,mask = mask) #plot the resultplt.imshow(final)

在上圖中,我們可以看到使用分水嶺算法計算的輸入圖像及其分割蒙版。此外,我們應用了按位“與”運算以從圖像中刪除背景並從圖像中提取相關部分。

邊緣檢測

邊緣是圖像中圖像亮度急劇變化或不連續的點。這種不連續通常對應於:

  • 深度不連續

  • 表面取向不連續

  • 材料特性的變化

  • 場景照明的變化

邊緣是圖像的非常有用的功能,可以用於不同的應用程序,例如圖像中的對象分類和定位。甚至深度學習模型也會計算邊緣特徵,以提取有關圖像中存在的對象的信息。

邊緣與輪廓不同,因爲它們與對象無關,而是表示圖像像素值的變化。邊緣檢測可用於圖像分割甚至圖像銳化。​​​​​​​

#import the required librariesimport numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline#read the imageimage = cv2.imread('coins.jpg') #calculate the edges using Canny edge algorithmedges = cv2.Canny(image,100,200) #plot the edgesplt.imshow(edges)

圖像過濾

在圖像過濾中,使用像素的鄰近值更新像素值。但是這些值首先如何更新?

嗯,有多種更新像素值的方法,例如從鄰居中選擇最大值,使用鄰居的平均值等。每種方法都有其自己的用途。例如,將鄰域中的像素值平均用於圖像模糊。

高斯濾波還用於圖像模糊,該模糊基於相鄰像素與所考慮像素的距離爲它們賦予不同的權重。

對於圖像過濾,我們使用內核。內核是具有不同形狀的數字矩陣,例如3 x 3、5 x 5等。內核用於計算帶有圖像一部分的點積。在計算像素的新值時,內核中心與像素重疊。相鄰像素值與內核中的相應值相乘。將計算出的值分配給與內核中心一致的像素。​​​​​​​

#importing the required libraries import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline image = cv2.imread('index.png') #using the averaging kernel for image smoothening averaging_kernel = np.ones((3,3),np.float32)/9 filtered_image = cv2.filter2D(image,-1,kernel) plt.imshow(dst) #get a one dimensional Gaussian Kernel gaussian_kernel_x = cv2.getGaussianKernel(5,1) gaussian_kernel_y = cv2.getGaussianKernel(5,1) #converting to two dimensional kernel using matrix multiplication gaussian_kernel = gaussian_kernel_x * gaussian_kernel_y.T #you can also use cv2.GaussianBLurring(image,(shape of kernel),standard deviation) instead of cv2.filter2D filtered_image = cv2.filter2D(image,-1,gaussian_kernel) plt.imshow()

在上面的輸出中,右側的圖像顯示了在輸入圖像上應用高斯核的結果。我們可以看到原始圖像的邊緣被抑制了。具有不同sigma值的高斯核被廣泛用於計算我們圖像的高斯差。這是特徵提取過程中的重要步驟,因爲它可以減少圖像中存在的噪點。

影像輪廓

輪廓是點或線段的閉合曲線,代表圖像中對象的邊界。輪廓實質上是圖像中對象的形狀。

與邊緣不同,輪廓不是圖像的一部分。相反,它們是與圖像中對象形狀相對應的點和線段的抽象集合。

我們可以使用輪廓來計算圖像中對象的數量,根據對象的形狀對對象進行分類,或者從圖像中選擇特定形狀的對象。​​​​​​​

#importing the required libraries import numpy as np import cv2 import matplotlib.pyplot as plt %matplotlib inline image = cv2.imread('shapes.png') #converting RGB image to Binary gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(gray_image,127,255,0) #calculate the contours from binary imageim,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) with_contours = cv2.drawContours(image,contours,-1,(0,255,0),3) plt.imshow(with_contours)

尺度不變特徵變換(SIFT)

關鍵點是在處理圖像時應注意的概念。這些基本上是圖像中的關注點。關鍵點類似於給定圖像的特徵。

它們是定義圖像中有趣內容的位置。關鍵點很重要,因爲無論如何修改圖像(旋轉,縮小,擴展,變形),我們始終會爲圖像找到相同的關鍵點。

尺度不變特徵變換(SIFT)是一種非常流行的關鍵點檢測算法。它包括以下步驟:

  • 尺度空間極值檢測

  • 關鍵點本地化

  • 方向分配

  • 關鍵點描述符

  • 關鍵點匹配

從SIFT提取的特徵可用於圖像拼接,物體檢測等應用。以下代碼和輸出顯示了關鍵點及其使用SIFT計算得出的方向。​​​​​​​

#import required librariesimport cv2import numpy as npimport matplotlib.pyplot as plt%matplotlib inline#show OpenCV versionprint(cv2.__version__)#read the iamge and convert to grayscaleimage = cv2.imread('index.png')gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#create sift objectsift  = cv2.xfeatures2d.SIFT_create()#calculate keypoints and their orientationkeypoints,descriptors = sift.detectAndCompute(gray,None)#plot keypoints on the imagewith_keypoints = cv2.drawKeypoints(gray,keypoints)#plot the imageplt.imshow(with_keypoints)

SURF

SURF是SIFT的增強版本。它的工作速度更快,並且對圖像轉換更健壯。在SIFT中,比例空間使用高斯的拉普拉斯算子近似。什麼是高斯的拉普拉斯算子?

拉普拉斯算子是用於計算圖像邊緣的內核。拉普拉斯核通過近似圖像的二階導數來工作。因此,它對噪聲非常敏感。我們通常將高斯核應用於拉普拉斯核之前的圖像,因此將其命名爲高斯拉普拉斯。

在SURF中,高斯的拉普拉斯算子是使用盒式濾波器(核)來計算的。盒式濾波器的卷積可以針對不同的比例並行進行,這是SURF速度提高的根本原因(與SIFT相比)。​​​​​​​

#import required librariesimport cv2import numpy as npimport matplotlib.pyplot as plt%matplotlib inline#show OpenCV versionprint(cv2.__version__)#read image and convert to grayscaleimage = cv2.imread('index.png')gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#instantiate surf objectsurf  = cv2.xfeatures2d.SURF_create(400)#calculate keypoints and their orientationkeypoints,descriptors = surf.detectAndCompute(gray,None)
with_keypoints = cv2.drawKeypoints(gray,keypoints)
plt.imshow(with_keypoints)

特徵匹配

可以匹配使用SIFT或SURF從不同圖像中提取的特徵,以找到存在於不同圖像中的相似對象/圖案。OpenCV庫支持多種功能匹配算法,例如蠻力匹配,knn特徵匹配等。​​​​​​​

import numpy as npimport cv2import matplotlib.pyplot as plt%matplotlib inline
#reading images in grayscale formatimage1 = cv2.imread('messi.jpg',0)image2 = cv2.imread('team.jpg',0)
#finding out the keypoints and their descriptorskeypoints1,descriptors1 = cv2.detectAndCompute(image1,None)keypoints2,descriptors2 = cv2.detectAndCompute(image2,None)
#matching the descriptors from both the images bf = cv2.BFMatcher()matches = bf.knnMatch(ds1,ds2,k = 2)

#selecting only the good featuresgood_matches = []for m,n in matches:    if m.distance < 0.75*n.distance:        good.append([m])
image3 = cv2.drawMatchesKnn(image1,kp1,image2,kp2,good,flags = 2)

在上圖中,我們可以看到從原始圖像(左側)提取的關鍵點與其旋轉版本的關鍵點匹配。這是因爲特徵是使用SIFT提取的,而SIFT對於此類變換是不變的。

人臉檢測

OpenCV支持基於haar級聯的對象檢測。Haar級聯是基於機器學習的分類器,可計算圖像中的不同特徵(如邊緣,線條等)。然後,這些分類器使用多個正樣本和負樣本進行訓練。

OpenCV Github存儲庫中提供了針對不同對象(如面部,眼睛等)的訓練分類器,你還可以針對任何對象訓練自己的haar級聯。​​​​​​​

#import required librariesimport numpy as npimport cv2 as cvimport matplotlib.pyplot as plt%matplotlib inline
#load the classifiers downloaded face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')eye_cascade = cv.CascadeClassifier('haarcascade_eye.xml')#read the image and convert to grayscale formatimg = cv.imread('rotated_face.jpg')gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)#calculate coordinates faces = face_cascade.detectMultiScale(gray, 1.1, 4)for (x,y,w,h) in faces:    cv.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)    roi_gray = gray[y:y+h, x:x+w]    roi_color = img[y:y+h, x:x+w]    eyes = eye_cascade.detectMultiScale(roi_gray)    #draw bounding boxes around detected features    for (ex,ey,ew,eh) in eyes:        cv.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)#plot the imageplt.imshow(img)#write image cv2.imwrite('face_detection.jpg',img)

關於更多機器學習、人工智能、增強現實資源和技術乾貨,可以關注公衆號:AIRX社區,共同學習,一起進步!

 

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