使用 Python 從視頻片段中確定足球球員球衣的顏色
使用 K-Means 聚類來識別球員球衣顏色
足球是世界上最受歡迎的運動。在洪都拉斯,足球能夠吸引大眾的注意力,并在 90 分鐘內(nèi)讓人群陷入情緒的漩渦。
多年來,我們看到各種技術(shù)被實施,以獲取有關(guān)比賽內(nèi)事件和球員表現(xiàn)的各種統(tǒng)計數(shù)據(jù)和信息。
通常,不僅為足球,而且為許多其他運動開發(fā)/實施的最有趣的技術(shù)應用程序之一是計算機視覺。計算機視覺 (CV) 是有關(guān)開發(fā)能夠理解圖像或視頻等視覺數(shù)據(jù)的算法和/或人工智能的領(lǐng)域。CV 非常強大,在 Instagram 過濾器、自動駕駛汽車、MRI 重建、癌癥檢測等許多應用中都很常見。
在這個項目中,我們在不同的足球比賽中拍攝了一系列視頻片段,并使用 K-Means 聚類算法確定了球員球衣顏色的顏色。
本文將詳細介紹實現(xiàn)該目標的過程。這里開發(fā)的例程將視頻片段作為輸入,并生成一個包含聚類過程結(jié)果的 pandas 數(shù)據(jù)幀作為輸出。
這個項目需要執(zhí)行數(shù)據(jù)清理、聚類、圖像/視頻處理、圖像中對象的基本分類、讀取 JSON 文件以及各種 pandas/numpy 數(shù)組/列表操作。
本文目錄:
· 圖像處理基礎
· 從圖像中提取顏色
· 從視頻文件中提取幀
· 從JSON文件中提取播放器邊界框
· 實現(xiàn)K-Means聚類確定球員球衣顏色
· 制作用于快速可視化聚類結(jié)果的GUI
· 結(jié)論
讓我們開始吧!
圖像/視頻處理基礎
本節(jié)將介紹對本項目很重要的圖像和視頻處理/操作的基礎知識。
使用足球歷史上我最喜歡的時刻之一作為參考圖像來嘗試各種可用的處理技術(shù)。那一刻是羅納爾迪尼奧在 2005 年 11 月 19 日效力于巴塞羅那足球俱樂部時,對陣皇家馬德里的精彩進球,如下圖所示。
2005 年 11 月 19 日,羅納爾迪尼奧對皇家馬德里的進球。
使用 OpenCV 加載圖像
需要做的第一件事是將圖像加載到筆記本中。如果你將圖像保存在計算機上,則可以簡單地使用cv2.imread函數(shù)。但是,對于在這部分工作中使用的圖像,是通過 URL 獲取的。然后,加載圖像需要我們:
1. 將我們的 URL 傳入urllib.request.urlopen
2. 從 URL 中的圖像創(chuàng)建一個 numpy 數(shù)組
3. cv2.imdecode用于從內(nèi)存緩存中讀取圖像數(shù)據(jù),并將其轉(zhuǎn)換為圖像格式。
4. 由于cv2.imdecode默認以 BGR 格式加載圖像,因此我將使用cv2.cvtColor(img, cv2.COLOR_BGR2RGB)原始 RGB 處理和渲染圖像。
#Render image from URL
req = urllib.request.urlopen('https://www.sportbible.com/cdn-cgi/image/width=648,quality=70,format=webp,fit=pad,dpr=1/https%3A%2F%2Fs3-images.sportbible.com%2Fs3%2Fcontent%2Fcf2701795dd2a49b4d404d9fa38f99fd.jpg')
arr = np.a(chǎn)sarray(bytearray(req.read()), dtype=np.uint8)
bgr_img = cv2.imdecode(arr, -1) # 'Load it as it is'
# Determine the figures size in inches to fit image
dpi = plt.rcParams['figure.dpi']
height, width, depth = bgr_img.shape
figsize = width / float(dpi), height / float(dpi)
plt.figure(figsize=figsize)
plt.imshow(bgr_img)
plt.show()
此過程的結(jié)果如下所示:
使用 OpenCV 在 BGR 空間中加載的圖像。
如你所見,加載的此圖像中的顏色與原始圖像中看到的顏色不匹配。這是因為 OpenCV 在 BGR 顏色空間中默認加載的圖像。
不過問題不大,因為切換到 RGB 顏色空間可以通過快速的代碼行來完成,如下所示:
#Convert image to RGB from BGR
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=figsize)
plt.imshow(rgb_img)
plt.show()
使用 OpenCV 在 BGR 空間中加載的圖像。
現(xiàn)在可以開始使用各種圖像處理/操作技術(shù)了。將在這里展示其中的一些!
旋轉(zhuǎn)圖像
有幾種不同的方法可以旋轉(zhuǎn)圖像。imutils 包通過imutils.rotate_bound函數(shù)具有最簡單的實現(xiàn),因為它所需要的只是要旋轉(zhuǎn)的圖像,以及我們要旋轉(zhuǎn)圖像的角度。
除此之外,此功能確保顯示的旋轉(zhuǎn)圖像不會被裁剪并完全包含在邊界內(nèi)。其他方法需要首先構(gòu)建旋轉(zhuǎn)矩陣,然后應用旋轉(zhuǎn)矩陣。
#Rotating an image
rotated0 = imutils.rotate_bound(rgb_img,0)
rotated45 = imutils.rotate_bound(rgb_img,45)
rotated90 = imutils.rotate_bound(rgb_img,90)
fig,axs = plt.subplots(1,3, figsize=(30,15))
axs[0].imshow(rotated0)
axs[1].imshow(rotated45)
axs[2].imshow(rotated90)
plt.show()
此操作的結(jié)果如下所示:
在 Python 中旋轉(zhuǎn)圖像。
裁剪圖像
通過 OpenCV 加載圖像時,圖像被加載為 numpy 數(shù)組。然后,要裁剪圖像,我們可以簡單地使用 numpy 切片來裁剪內(nèi)容。
我們有多種裁剪的方法。將在這里展示一個簡單的示例,我們可以按不同的高度和寬度百分比裁剪圖像。通過定義感興趣區(qū)域 (ROI) 和輪廓,將在后面的部分中展示更多的方法來裁剪。
#Need to find the starting/ending column and row index first for the desired cropping
cropIni = [0.15,0.3,0.45]
#Crop width and height of image by 15% each
startRow1 = int(height*cropIni[0]) ;startCol1 = int(width*cropIni[0])
endRow1 = int(height*(1-cropIni[0])) ;endCol1 = int(width*(1-cropIni[0]))
#Crop width and height of image by 30% each
startRow2= int(height*cropIni[1]) ;startCol2 = int(width*cropIni[1])
endRow2 = int(height*(1-cropIni[1])) ;endCol2 = int(width*(1-cropIni[1]))
#Crop width and height of image by 40% each
startRow3 = int(height*cropIni[2]) ;startCol3 = int(width*cropIni[2])
endRow3 = int(height*(1-cropIni[2])) ;endCol3 = int(width*(1-cropIni[2]))
#This is just slicing the array
fig,axs = plt.subplots(1,3, figsize=(30,15))
crop1 = rgb_img[startRow1:endRow1, startCol1:endCol1]
crop2 = rgb_img[startRow2:endRow2, startCol2:endCol2]
crop3 = rgb_img[startRow3:endRow3, startCol3:endCol3]
axs[0].imshow(crop1)
axs[1].imshow(crop2)
axs[2].imshow(crop3)
plt.show()
通過 Python 中的 numpy 切片裁剪圖像。
調(diào)整圖像大小
調(diào)整圖像大小的方法有很多。在這里,將展示如何使用 OpenCV 中的 resize 函數(shù)調(diào)整圖像大小。盡管圖像看起來相同,但可以看出,當我們調(diào)整圖像大小時,圖像的大。ǜ叨群蛯挾龋⿻l(fā)生變化。
#Resizing an image
#cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
xscale = [0.75,0.5,0.25]
yscale = [0.75,0.5,0.25]
rimg1 = cv2.resize(rgb_img, (0,0), fx=xscale[0], fy=y(tǒng)scale[0])
rimg2 = cv2.resize(rgb_img, (0,0), fx=xscale[1], fy=y(tǒng)scale[1])
rimg3 = cv2.resize(rgb_img, (0,0), fx=xscale[2], fy=y(tǒng)scale[2])
fig,axs = plt.subplots(1,3, figsize=(30,15))
axs[0].imshow(rimg1)
axs[1].imshow(rimg2)
axs[2].imshow(rimg3)
plt.show()
print("The width, height and depth of this image are ",rimg1.shape)
print("The width, height and depth of this image are ",rimg2.shape)
print("The width, height and depth of this image are ",rimg3.shape)
在 Python 中調(diào)整圖像大小。
The width, height and depth of this image are (304, 486, 3)
The width, height and depth of this image are (202, 324, 3)
The width, height and depth of this image are (101, 162, 3)
調(diào)整圖像的亮度/對比度
可以通過OpenCV 中的addWeighted功能來調(diào)整圖像的亮度/對比度。這是一個稱為混合的過程。此函數(shù)使用以下轉(zhuǎn)換對圖像進行這些調(diào)整:
result = αsrc1 + βsrc2 + γ
在上面的等式中,通過將α值應用于源圖像、將β值應用于其他圖像(它可以是相同的源圖像)并將其值增加來修改混合圖像γ。
混合效果如下圖所示。
第一行圖顯示了α在保持其他兩個參數(shù)不變的情況下變化的效果(α從左到右遞減)。
第二行圖顯示了β在保持其他兩個參數(shù)不變的情況下變化的效果(β從左到右增加)。
第三行圖顯示了γ在保持其他兩個參數(shù)不變的情況下變化的效果(γ從左到右增加)。
· 減小α使圖像變暗。
· 增加β使圖像具有更強的對比度。
· 減小γ使圖像柔化。
#cv2.a(chǎn)ddWeighted(source_img1, alpha, source_img2, beta, gamma)
alpha = [0.75, 0.5, 0.25]
beta = [0, 1 , 10]
gamma = [0, 10 ,100]
#Vary alpha
alpha_img1 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[0], gamma[0])
alpha_img2 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[1], rgb_img, beta[0], gamma[0])
alpha_img3 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[2], rgb_img, beta[0], gamma[0])
#Vary beta
beta_img1 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[0], gamma[0])
beta_img2 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[1], gamma[0])
beta_img3 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[2], gamma[0])
#Vary gamma
gamma_img1 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[0], gamma[0])
gamma_img2 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[0], gamma[1])
gamma_img3 = cv2.a(chǎn)ddWeighted(rgb_img, alpha[0], rgb_img, beta[0], gamma[2])
在 Python 中更改圖像的亮度和對比度。
更改圖像的色彩空間
圖像處理中使用了多種顏色空間,可以促進各種任務,例如邊緣檢測、顏色檢測和應用蒙版等等。
使用 OpenCV 通過cvtColor函數(shù)可以很容易地在顏色空間之間進行轉(zhuǎn)換
下面列出了一些常見的色彩空間:
· RGB -> 許多圖像最初都是使用這種格式編碼的
· HSV -> 提供對顏色色調(diào)的更好控制
· 灰色 -> 使許多圖像處理方法更準確
改變顏色空間的一些示例如下所示:
gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2GRAY)
bgr_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2BGR)
hsv_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)
在 Python 中更改顏色空間。
圖像模糊
當試圖檢測邊緣(即描繪從一組像素到另一組像素的過渡的線條)時,模糊是一項重要的操作,因為它使對象邊界之間的過渡更加平滑。例如,這可用于將對象與背景分離。
為這個項目研究了四個類別:
· 平均模糊 -> 快速但可能無法保留對象邊緣
· 高斯模糊 -> 比平均模糊慢,但邊緣保留得更好
· 中值過濾 -> 對異常值具有魯棒性
· 雙邊過濾 -> 比上述方法慢得多。更多參數(shù)(更可調(diào))。
使用不同模糊方法的效果如下圖所示。
第一行圖顯示了使用平均模糊同時從左到右增加內(nèi)核大小的效果。
第二行圖顯示了使用高斯模糊同時從左到右增加內(nèi)核大小的效果。
第三行圖顯示了使用中值模糊同時從左到右增加內(nèi)核大小的效果。
第四行圖顯示了使用雙邊模糊同時從左到右增加sigmaSpace、diameter和sigmaColor參數(shù)的效果。
params = [(3, 20, 5, 5), (9, 20, 40, 20), (15, 20, 160, 60)]
fig,axs = plt.subplots(4, 3, figsize=(30,30))
i = 0
for (k, diameter, sigmaColor, sigmaSpace) in params:
simpleblur_image = cv2.blur(rgb_img, (k,k))
gaussblur_image = cv2.GaussianBlur(rgb_img, (k,k), 0)
medianblur_image = cv2.medianBlur(rgb_img, k)
bilateralblur_image = cv2.bilateralFilter(rgb_img, diameter, sigmaColor, sigmaSpace)
axs[0,i].imshow(simpleblur_image)
axs[1,i].imshow(gaussblur_image)
axs[2,i].imshow(medianblur_image)
axs[3,i].imshow(bilateralblur_image)
i+=1
#Plot results
plt.show()
檢測圖像中的邊緣
邊緣檢測是一種識別圖像內(nèi)對象邊界(即邊緣)的圖像處理技術(shù)。邊緣使我們能夠識別圖像的底層結(jié)構(gòu),使它們成為我們需要從圖像中獲取的最重要信息之一。
下面使用 Canny 算法來檢測圖像上的邊緣。
#cv2.Canny(image, minVal, maxVal)
img_gray = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2GRAY)
thresholds = [(5,150), (100,150), (200,225)]
fig,axs = plt.subplots(1,4, figsize=(30,15))
i = 0
axs[i].imshow(rgb_img)
for (minVal, maxVal) in thresholds:
edge_img = cv2.Canny(img_gray, minVal, maxVal, apertureSize = 3, L2gradient = False)
axs[i+1].imshow(edge_img)
i += 1
plt.show()
在 Python 中使用 Canny 進行邊緣檢測。
掩蓋圖像中的顏色(應用蒙版)
通常,人們可能只想在圖像中顯示特定的顏色。這可以通過蒙版來實現(xiàn)。
OpenCV 中的inRange功能允許在 HSV 空間中完成此操作。
下面顯示的圖像(從左到右)分別是未應用蒙版、蒙版綠色、紅色和藍色的結(jié)果。
#Remove green background/field from image prior to clustering
green = np.a(chǎn)rray([60,255,255]) #This is green in HSV
loGreen = np.a(chǎn)rray([30,25,25]) #low green threshold
hiGreen = np.a(chǎn)rray([90,255,255]) #Upper green threshold
loBlue = np.a(chǎn)rray([0,25,25]) #low red threshold
hiBlue = np.a(chǎn)rray([30,255,255]) #Upper red threshold
loRed = np.a(chǎn)rray([120,25,25]) #low blue threshold
hiRed = np.a(chǎn)rray([180,255,255]) #Upper blue threshold
#Convert image to HSV
hsv = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2HSV)
gmask = cv2.inRange(hsv, loGreen, hiGreen)
rmask = cv2.inRange(hsv, loRed , hiRed)
bmask = cv2.inRange(hsv, loBlue , hiBlue)
gresult = rgb_img.copy()
bresult = rgb_img.copy()
rresult = rgb_img.copy()
gresult[gmask==255] = (255,255,255)
bresult[bmask==255] = (255,255,255)
rresult[rmask==255] = (255,255,255)
在 Python 中掩蓋顏色。
選擇圖像中的感興趣區(qū)域
選擇 ROI 是另一種形式的裁剪。如果你不想處理太多圖像,此處顯示的方法是快速裁剪圖像的好方法。
#Select ROI from image
imagedraw = cv2.selectROI('select',rgb_img)
cv2.waitKey(0)
cv2.destroyWindow('select')
#cropping the area of the image within the bounding box using imCrop() function
roi_image = rgb_img[int(imagedraw[1]):int(imagedraw[1]+imagedraw[3]),
int(imagedraw[0]):int(imagedraw[0]+imagedraw[2])]
fig,axs = plt.subplots(1,1, figsize=(5,5))
axs.imshow(roi_image)
plt.show()
ROI 圖像。
從圖像中提取顏色
在這一點上,已經(jīng)介紹了許多基本的處理操作,這些操作應該足以從圖像中確定球員球衣顏色。為了確定顏色,嘗試了以下方法:
· 在單個像素處提取顏色
· 通過逐像素平均提取顏色
· 使用 K-Means 聚類獲得圖像中的 k-colors
在單個像素處提取顏色
通過將像素的 x,y 坐標提供給圖像數(shù)組,讀取該像素的 RGB 通道的結(jié)果,并將這些 RGB 通道分配到一個數(shù)組中,可以輕松地提取單個像素的顏色。
寫了一個小函數(shù)來處理下圖中的各種像素。下面顯示了 17 個不同像素的結(jié)果。
#Get color from single pixel in image
#Make list of pixel coordinates based on image shape
y = range(0, height, 25)
x = range(0, width, 25)
#Combine lists above into a list of tuples
merged_list = tuple(zip(x, y))
#Initialize the plot
fig,axs = plt.subplots(1, len(y), figsize=(30,30))
i = 0
#Iterate over elements in tuple list of pixel coordinates
for (x, y) in merged_list:
#Return rgb tuple at x,y coordinate
r, g, b = (rgb_img[x, y])
# Creating rgb array from rgb tuple
color_of_pix = np.zeros((5, 5, 3), np.uint8)
color_of_pix[:] = [r, g, b]
#Display rgb array
axs[i].imshow(color_of_pix)
i += 1
plt.show()
提取像素顏色的方法1。
通過逐像素平均提取主色
現(xiàn)在我們可以提取單個像素的顏色,我們可以擴展該方法來確定圖像的平均顏色。將 x, y 坐標傳遞給我們的圖像數(shù)組會返回一個像素的 RGB 元組。
然后,通過在每個像素處添加元組中每個元素的值,我們可以獲得與每個 RGB 通道相關(guān)的“總計數(shù)”。最后,我們可以將每個 RGB 顏色通道中的計數(shù)除以圖像中的像素總數(shù),以獲得圖像的平均顏色。
#Determining most frequently occurring color pixel by pixel
def most_common_used_color(img):
# Get width and height of Image
height, width, depth = img.shape
# Initialize Variable
r_total = 0
g_total = 0
b_total = 0
count = 0
# Iterate through each pixel
for x in range(0, height):
for y in range(0, width):
# r,g,b value of pixel
r, g, b = (img[x, y])
r_total += r
g_total += g
b_total += b
count += 1
return (r_total/count, g_total/count, b_total/count)
這個過程的結(jié)果如下所示,其中平均顏色變成了一個 HEX 值為#787561的灰綠色,通過對圖像的視覺檢查,這看起來是合理的。但是,我們可以改善這一點嗎?
圖像中最常見的用戶顏色由平均確定。
通過 K-Means 聚類提取主色
K-Means 聚類算法可以進一步改進球員球衣顏色檢測程序。該例程將允許我們通過指定例程應使用的簇數(shù)k來提取圖像中的幾種“主要顏色”。如果知道數(shù)據(jù)應該屬于多少個集群,則可以先驗地確定k的值。
否則,確定k值的常用方法是通過肘部法,如下所示。圖表的拐點(又名肘部)是應該使用的 k 值。
肘部圖的結(jié)果表明,最佳 k 值為3.
#Determine optimal k value for clustering using elbow method
distortions = [] #Initialize array with distortions from each clustering run
K = range(1,11) #Explore k values between 1 and 10
#Run the clustering routine
for k in K:
#Convert image into a 1D array
flat_img = np.reshape(rgb_img,(-1,3))
kmeanModel = KMeans(n_clusters=k)
kmeanModel.fit(flat_img)
distortions.a(chǎn)ppend(kmeanModel.inertia_)
進行肘部法的結(jié)果。
在圖像上運行 k-means 聚類
確定k應該是3后,可以編寫一個小程序來拍攝圖像并確定其 k 主導顏色。
k = 3、k = 4和k = 10(只是為了搞笑而取的4和10)案例的結(jié)果如下所示:
def KMeansTest(img,clusters):
"""
Args:
path2img : (str) path to cropped player bounding box
clusters : (int) how many clusters to use for KMEANS
Returns:
rgb_array : (tuple) Dominant colors in image in RGB format
"""
org_img = img.copy()
#print('Org image shape --> ',img.shape)
#Convert image into a 1D array
flat_img = np.reshape(img,(-1,3))
arrayLen = flat_img.shape
#Do the clustering
kmeans = KMeans(n_clusters = clusters, random_state=0, tol = 1e-4)
kmeans.fit(flat_img)
#Define the array with centroids
dominant_colors = np.a(chǎn)rray(kmeans.cluster_centers_,dtype='uint')
#Calculate percentages
percentages = (np.unique(kmeans.labels_,return_counts=True)[1])/flat_img.shape[0]
#Combine centroids representing dominant colors and percentages associated with each centroid into an array
pc = list(zip(percentages,dominant_colors))
pc = sorted(pc,reverse=True)
i = 0
rgb_array = []
for i in range(clusters):
dummy_array = pc[i][1]
rgb_array.a(chǎn)ppend(dummy_array)
i += 1
return rgb_array
#Call K-Means function with K = 3
nClusters = 3
rgb_array = KMeansTest(rgb_img, nClusters)
plotKMeansResult(nClusters,rgb_array)
從 K-Means 聚類確定的圖像中的前三種顏色。圖像中的色彩流行度從左到右遞減。
使用k=3確定的頂部顏色是綠色,在目視檢查時,考慮到“綠色區(qū)域”的普遍存在,它看起來是正確的。
#Call K-Means function with K = 4
nClusters = 4
rgb_array = KMeansTest(rgb_img, nClusters)
plotKMeansResult(nClusters,rgb_array)
從 K-Means 聚類確定的圖像中的前四種顏色。圖像中的色彩流行度從左到右遞減。
使用k=4確定的頂部顏色也是綠色。然而,它是一個更明亮的陰影。
#Call K-Means function with K = 10
nClusters = 10
rgb_array = KMeansTest(rgb_img, nClusters)
plotKMeansResult(nClusters,rgb_array)
從 K-Means 聚類確定的圖像中的前 10 種顏色。圖像中的色彩流行度從左到右遞減。
使用k=10確定的頂部顏色也是綠色。但是,它比前兩個示例要亮得多。從k=10的使用可以看出,可以通過使用更多的簇來獲得更高的顏色特異性。
要點是 k-means 例程可以準確地檢測顏色并提供圖像中最常出現(xiàn)的顏色。
識別圖像中的人
在開始處理視頻片段之前,還對另一件事感興趣。想檢查圖像中球員/人的分類和/或識別。這些對象的分類不是作業(yè)的一部分,但想簡要探討一下以滿足我的好奇心。
OpenCV 中的 HOG 包包含訓練模型的數(shù)據(jù)庫,這些模型能夠檢測不同的對象,如貓、臉和人類。在以后的文章中,將展示為解決這個分類問題而構(gòu)建的神經(jīng)網(wǎng)絡模型。但現(xiàn)在,將展示 HOG 包的用法。
#Detecting humans with HOG
path2xml = r'C:UsersmurcDocumentsGitHubopencvdatahaarcascadeshaarcascade_fullbody.xml'
fbCascade = cv2.CascadeClassifier(path2xml)
# Initializing the HOG person detector
image = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2GRAY)
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
# Resizing the Image
image = imutils.resize(image, width = min(1000, image.shape[1]))
# Detecting all the regions in the image that has a person inside it
#(regions, _) = hog.detectMultiScale(image, winStride = (2,2), padding = (4, 4), scale = 1.1)
players = fbCascade.detectMultiScale(image, scaleFactor = 1.005, minSize=(20, 20), minNeighbors = 1)
image2 = rgb_img.copy()
# Drawing the regions in the Image
i=0
for (x, y, w, h) in players:
cv2.rectangle(image2, (x, y), (x + w, y + h), (0, 255, 0), 3)
currentbox = image2[y:y+h,x:x+w]
i+=1
在 Python 中使用 HOG 包檢測圖像中的球員失敗。
花了一些時間研究檢測器的參數(shù),并沒有得到更好的結(jié)果。嘗試使用DefaultPeopleDetector和Haar 級聯(lián)分類器haarcascade_fullbody,但無法得到想要的結(jié)果。
盡管檢測球員本身不是項目的一部分(得到了一個包含球員邊界框坐標的 JSON 文件),但仍然想確保成功使用 HOG 檢測器。
在下面嘗試了一個不同的圖像,認為它可以讓我成功檢測。在嘗試了幾分鐘的參數(shù)后,我找到了一個有效的組合!決定僅使用球員邊界框 (BB) 生成圖像,并將 K-means 例程應用于該 BB 的內(nèi)容。結(jié)果如下所示:
使用 HOG 包檢測球員并提取包含球員的邊界框。
上圖中的前 4 種顏色是通過 K-Means 聚類確定的。
需要研究優(yōu)化/自動化對象檢測器功能的參數(shù),但對到目前為止的進展感到滿意。
處理視頻和提取幀
在熟悉了各種圖像處理和處理技術(shù)并了解如何實施 K-Means 以提取圖像中的主色后,我決定開始處理視頻片段,因為我確信我有開發(fā)的基礎,可以從圖像中確定球衣顏色。
部分文件涉及從兩個不同的攝像機拍攝的足球比賽的視頻片段。需要做的第一件事是獲取視頻文件,使用以下代碼:
def getListOfFiles(rPath , fType):
"""
Args:
rPath: (str) path to file
fType: (str) type of file to look for (i.e., .mp4, .json, etc.)
Returns:
lFiles: (list) List of files in rPath of type fType
"""
#1. Establish the current working directory
directory = os.getcwd()
#2. List all files in rPath of type fType
lFiles = glob.glob(directory + rPath + "*" + fType)
return lFiles
現(xiàn)在有了 mp4 文件的列表,可以從視頻中提取單個幀:
def get_frame(video_file, frame_index):
"""
Args:
video_file: (str) path to .MP4 video file
frame_index: (int) query frame index
Returns:
frame: (ndarray, size (y, x, 3)) video frame
Uses OpenCV BGR channels
"""
video_capture = cv2.VideoCapture(video_file)
video_capture.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
success, frame = video_capture.read()
if not success:
raise ValueError(
"Couldn't retrieve frame {0} from video {1}".format(
frame_index,
video_file
)
)
return frame
現(xiàn)在可以可視化從兩個相機中提取的幀。幀 2500 如下所示:
分別從左右相機中提取幀。
想從視頻中提取的另一件事是其中的幀數(shù)。這可以使用以下代碼來完成:
#Determine number of frames in video
def count_frames(video_file):
"""
Args:
video_file: (str) path to .MP4 video file
Returns:
nFrames: (int) Number of frames in mp4
"""
cap = cv2.VideoCapture(video_file)
length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
return(length)
加載 JSON 文件并檢查邊界框
JSON 文件包含球員邊界框坐標。需要做的第一件事是加載 JSON 文件。
JSON 文件與 MP4 文件相關(guān)聯(lián),因此必須確保在批處理所有文件時,正確的 JSON 文件與正確的 MP4 文件配對。
為此,將首先從之前生成的文件名列表中刪除路徑和.json擴展名,并將結(jié)果放入名為json_strip的列表中。
然后,將從先前生成的每個相機(LCAMERA和RCAMERA)的MP4文件列表中刪除路徑,并將結(jié)果分別放入兩個名為MP4_strip_LC和MP4_strap_RC的列表中。
最后,將使用這些列表從每臺攝像機中獲取與這些文件相關(guān)聯(lián)的索引。完成上述步驟的例程如下所示:
def matchJSON2MP4(jsonList, jsonPath, MP4list, MP4Path, whichMP4):
json_strip = [s.replace(directory + jsonPath + '\', '') for s in jsonList]
json_strip = [s.replace(".json", '') for s in json_strip]
mp4_strip = [s.replace(directory + MP4Path + '\', '') for s in MP4list]
mp4Name = mp4_strip[whichMP4]
index = json_strip.index(mp4Name)
print(index)
return index
可以讀取 JSON 文件并使用上面的框架獲取綁定框信息。為此,將為正在處理的視頻中的每個幀生成一個字典,其中包含每個球員邊界框(檢測)的坐標。
#Get dictionary from json file
def read_json_dict(path2json):
"""
Args:
path2json: (str) path to .MP4 json file containing player bounding boxes
Returns:
bb_dict: (dict) Dictionary containing bounding boxes in each frame
"""
# Opening JSON file
f = open(path2json)
# Returns JSON object as a dictionary
bb_dict = json.load(f)
f.close()
return(bb_dict)
上面的代碼為提供了當前視頻中每一幀的邊界框。
接下來,將確定給定幀中有多少個邊界框。
#Determine number of bounding boxes in frame
def count_bboxes(bb_dict,frame_index):
"""
Args:
bb_dict: (dict) dictionary from json file
frame: (int) what frame is being processed
Returns:
nDetections: (int) Number of bounding boxes in frame
"""
bbs = bb_dict['frames'][frame_index]['detections']
nDetections = len(bbs)
#print(nDetections, " bounding boxes found in frame ", frame_index)
return(nDetections)
接下來,將確定視頻中包含球員檢測的第一幀。
#Find first frame that contains detections
def findFirstFrame(bb_dict):
"""
Args:
bb_dict: (dict) dictionary from json file
Returns:
firstFrame: (int) First frame to process in video
"""
firstFrame = bb_dict['frames'][0]['frame_index']
print('These is the first frame to process in video ', firstFrame)
return(firstFrame)
接下來,對不同視頻可能不同的frame_index值進行檢測。讓我們根據(jù) JSON 文件計算出視頻的檢測收集間隔。
#Find first frame that contains detections
def findFrameSpacing(bb_dict):
"""
Args:
bb_dict: (dict) dictionary from json file
Returns:
spacing: (int) Spacing between frames in json
"""
frame0 = bb_dict['frames'][0]['frame_index']
frame1 = bb_dict['frames'][1]['frame_index']
spacing = abs(frame1 - frame0)
print('The frame spacing is ', spacing)
return(spacing)
接下來,將從 JSON 文件中提取當前幀的所有邊界框坐標。
#Extract bounding boxes for a given frame from json
def get_bb4frame(bb_dict,frame_index):
"""
Args:
bb_dict: (dict) dictionary from json file
frame: (int) what frame is being processed
Returns:
nDetections: (int) Number of bounding boxes in frame
"""
bbs = bb_dict['frames'][frame_index]['detections']
#print('These are the coordinates for all bounding boxes in frame', frame_index)
#print(bbs)
return(bbs)
最后,將從 JSON 文件中提取特定邊界框的邊界框坐標。
#Extract bounding box coordinates for a specific bounding box in current frame from json
def makeRectangleFromJSON(bb_dict,whichBB):
"""
Args:
bb_dict: (dict) dictionary from json file
whichBB: (int) what bounding box is being processed
Returns:
x1 ,y1 ,x2 ,y2: (tuple) tuple containing pixel coordinates for the upper-left and lower-right corners of the bounding box
"""
x1 ,y1 ,x2 ,y2 = bb_dict[whichBB][0],bb_dict[whichBB][1],bb_dict[whichBB][2],bb_dict[whichBB][3]
#print(x1 ,y1 ,x2 ,y2, ' These are the coordinates for bounding box ', whichBB)
return(x1 ,y1 ,x2 ,y2)
讓我們通過可視化邊界框來看看我的例程是否有效!
這是視頻中第一幀的示例,其中分別包含對左右攝像頭的檢測。第一幀分別被確定為第 0 幀和第 62 幀。
來自左右攝像機的視頻素材的原始第一幀。
繪制了球員邊界框的幀如下所示。
來自帶有播放器邊界框的左右攝像機的視頻片段的幀。
最后,這是幀中每個球員的邊界框。
來自左側(cè)攝像頭的球員邊界框。
來自右側(cè)攝像機的球員邊界框。
到目前為止的方法允許我成功地提取球員邊界框。從一些邊界框可以看出一些東西。
首先,存在誤報的情況。此數(shù)據(jù)中的誤報意味著沒有球員的邊界框。這是未來需要解決的問題。
應用 K-Means 聚類確定球員球衣顏色
現(xiàn)在數(shù)據(jù)形狀正確,F(xiàn)在,讓我們嘗試在邊界框上應用 K-Means 聚類例程,看看會發(fā)生什么。將堅持處理到目前為止我一直在使用的相同視頻和幀,以便可以專注于聚類本身。
聚類例程如下所示。該例程需要以下步驟:
1. 將圖像(球員邊界框)轉(zhuǎn)換為 HSV 顏色空間
2. 將圖像展平為一維陣列,以便于處理
3. 運行 K 均值聚類
4. 確定圖像上每種顏色的百分比
5. 按降序?qū)@些顏色進行排序并將它們放入一個數(shù)組中
def KMeansImage(img, clusters):
"""
Args:
path2img : (str) path to cropped player bounding box
clusters : (int) how many clusters to use for KMEANS
Returns:
rgb_array : (tuple) Dominant colors in image in RGB format
"""
org_img = img.copy()
#Convert image to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#Convert image into a 1D array
flat_img = np.reshape(hsv,(-1,3))
arrayLen = flat_img.shape
rgb_array = []
#Do the clustering
kmeans = KMeans(n_clusters = clusters, random_state=0, tol = 1e-4)
kmeans.fit(flat_img)
#Define the array with centroids
dominant_colors = np.a(chǎn)rray(kmeans.cluster_centers_,dtype='uint')
#Calculate percentages
percentages = (np.unique(kmeans.labels_,return_counts=True)[1])/flat_img.shape[0]
#Combine centroids representing dominant colors and percentages
#associated with each centroid into an array
pc = list(zip(percentages,dominant_colors))
pc = sorted(reversed(pc), reverse = True, key = lambda x: x[0])
i = 0
for i in range(clusters):
#dummy_array = pc[i][1]
rgb_array.a(chǎn)ppend(pc[i][1])
i += 1
return rgb_array
在前四個邊界框上運行此例程的結(jié)果如下所示:
顯示的邊界框上的聚類結(jié)果。
上述結(jié)果的主要內(nèi)容之一是綠色是所有邊界框中的主要顏色。
綠色色調(diào)主要來自田野中存在的草。這就是蒙版將發(fā)揮作用的地方。
將蒙版應用于圖像數(shù)據(jù)
為了更好地處理邊界框中草場的存在,將使用蒙版。包括為構(gòu)成 HSV 顏色空間的三個值(即色調(diào)、飽和度、亮度)中的每一個設置低閾值和高閾值。如果一種顏色落在此閾值的范圍內(nèi),那么它將被屏蔽掉。
此外,添加了一些錯誤處理,用于蒙版過程刪除了太多像素的情況。聚類例程要求要處理的圖像至少具有與聚類一樣多的唯一像素。因此,如果生成的蒙版圖像的尺寸低于所需的簇數(shù),則該圖像將被忽略。這種情況很可能發(fā)生在邊界框只有一個字段的情況下。
def KMeansMaskGreen(img, clusters, lowHue, highHue, lowSat, highSat, loBright, hiBright):
"""
Args:
path2img : (str) path to cropped player bounding box
clusters : (int) how many clusters to use for KMEANS
Returns:
rgb_array : (tuple) Dominant colors in image in RGB format
"""
org_img = img.copy()
#print('Org image shape --> ',img.shape)
green = np.a(chǎn)rray([60,25,25])
loGreen = np.a(chǎn)rray([lowHue, lowSat, loBright]) #low green threshold
hiGreen = np.a(chǎn)rray([highHue, highSat, hiBright]) #Upper green threshold
#Convert image to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#Make the mask
mask = cv2.inRange(hsv, loGreen, hiGreen)
mask_img = img.copy()
mask_img[mask==255] = (255,255,255)
#Remove white pixels from image so that they don't interfere with the process
mask_img = mask_img[np.a(chǎn)ll(mask_img 。 255 , axis=-1)]
#Convert image into a 1D array
flat_img = np.reshape(mask_img,(-1,3))
arrayLen = flat_img.shape
#Ensure that masking didn't remove everything (Generally happens in false positives)
if mask_img.shape[0] <= clusters:
#print('Cropped image has dimensions lower than number of desired clusters.Not clustering current image')
rgb_array = np.empty((clusters,3,))
rgb_array[:] = np.nan
return rgb_array
else:
rgb_array = []
#Do the clustering
kmeans = KMeans(n_clusters = clusters, random_state=0, tol = 1e-4)
kmeans.fit(flat_img)
#Define the array with centroids
dominant_colors = np.a(chǎn)rray(kmeans.cluster_centers_,dtype='uint')
#Calculate percentages
percentages = (np.unique(kmeans.labels_,return_counts=True)[1])/flat_img.shape[0]
#Combine centroids representing dominant colors and percentages
#associated with each centroid into an array
pc = list(zip(percentages,dominant_colors))
pc = sorted(reversed(pc), reverse = True, key = lambda x: x[0])
i = 0
for i in range(clusters):
#dummy_array = pc[i][1]
rgb_array.a(chǎn)ppend(pc[i][1])
i += 1
return rgb_array
下面顯示了幾種不同情況下使用蒙版的結(jié)果。使用蒙版去除綠色對改進顏色檢測程序有很大幫助!
應用綠色蒙版后球員邊界框中的主要顏色。
移除邊界框的下半部分以專注于球衣數(shù)據(jù)
由于任務是僅確定球衣顏色,因此裁剪圖像的底部也有助于加強分析,因為我們可以專注于更重要的區(qū)域并提高球員球衣顏色檢測的準確性。這可以通過下面的代碼段簡單地完成。
def crop_image(image,howMuch):
"""
Args:
img : (array) image of player bounding box
howMuch : (int) percent of image to crop (between 0 and 100)
Returns:
cropped_img : (array) cropped image
"""
val = howMuch/100
cropped_img = image[0:int(image.shape[0]*val),0:int(image.shape[0])]
return cropped_img
應用蒙版和裁剪后的聚類例程的結(jié)果如下所示:
蒙版字段和裁剪圖像底部后的主要顏色。
處理整個 MP4 文件
讓我們嘗試處理每一幀,看看會發(fā)生什么!
到目前為止,已經(jīng)完成了所有例程并將它們放入下面的包裝函數(shù)中。
此包裝函數(shù)將 JSON 文件的路徑、mp4 文件的路徑、要處理的視頻以及 k-means 的聚類數(shù)作為輸入。
此函數(shù)的輸出是一個 pandas 數(shù)據(jù)幀,其中包含當前視頻每幀中每個邊界框的 RGB 格式的主色。
def getJerseyColorsFromMP4(jsonPath,MP4Path,whichVideo,nClusters):
#Make the list of mp4 and json files from each camera
print('Retrieving MP4 and JSON files...')
mp4List = getListOfFiles(MP4Path , ".mp4")
jsonList = getListOfFiles(jsonPath , ".json")
#Find the json file to use for the current video
jval = matchJSON2MP4(jsonList, jsonPath, mp4List, MP4Path, whichVideo)
#Get json dictionary of all bounding boxes in video
bb_dict = read_json_dict(jsonList[jval])#This is for first video in the LCamera folder
#Find first frame with detections
firstFrame = findFirstFrame(bb_dict)
#Determine frame spacing
frameSpacing = findFrameSpacing(bb_dict)
#Which frame to look at
whichFrame = 0
whichFrameAdj = firstFrame + whichFrame*frameSpacing #Adjust for video data to match json detection
nf = int(count_frames(mp4List[whichVideo])/10) #Number of frames in video
print('Initializing arrays...')
#Initialize arrays
dom_color1, dom_color2,dom_color3 = [],[],[]
frame_list,bb_list,video_list = [],[],[]
#Insert loop here for frames
print('Starting jersey color detection ...')
while whichFrameAdj < nf:
for i in tqdm(range(nf), desc="Processing Frame"):#Add progress bar for frames processed
#Get a frame from video
frame = get_frame(mp4List[whichVideo], whichFrameAdj)
#Convert color from BGR to RGB
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
#Make a copy of the frame to store for display of all the bounding boxes
frame_copy = frame.copy()
#Determine number of bounding boxes in current frame
n_bbs = count_bboxes(bb_dict,whichFrame)
#Get BB coordinates for current frame
bbs_frame = get_bb4frame(bb_dict,whichFrame) #BB coordinates for current frame
#Loop over bounding boxes in current frame
for bb in range(n_bbs):
#print('****Frame ' + str(whichFrameAdj) + ' BB ' + str(bb) + '****')
frame_list.a(chǎn)ppend(whichFrameAdj) #Append frame ID to list
bb_list.a(chǎn)ppend(bb)
video_list.a(chǎn)ppend(whichVideo)
x1 ,y1 ,x2 ,y2 = makeRectangleFromJSON(bbs_frame,bb) #Coordinates for current BB
currentbox = frame[y1:y2,x1:x2]
cv2.rectangle(frame_copy, (x1, y1), (x2, y2), (0, 0, 255), 2)
#Crop the bounding box
croped_bb = crop_image(currentbox,howMuch)
#Do the clustering
rgb_array = KMeansMaskGreen(croped_bb, nClusters,
lowHue, highHue, lowSat, highSat, loBright, hiBright)
#Append dominant RGB colors into respective arrays
dom_color1.a(chǎn)ppend(rgb_array[0])
dom_color2.a(chǎn)ppend(rgb_array[1])
dom_color3.a(chǎn)ppend(rgb_array[2])
whichFrame += 1
whichFrameAdj = firstFrame + whichFrame*frameSpacing #Adjust for video data to match json
print('Making pandas dataframe containing results...')
jerseyColor_df = pd.DataFrame({'Video ID': video_list,
'Frame ID': frame_list,
'BB in Frame': bb_list,
'Jersey Color 1': dom_color1,
'Jersey Color 2': dom_color2,
'Jersey Color 3': dom_color3})
print('PROCESS COMPLETED')
return jerseyColor_df
10 分鐘內(nèi)可處理 723 幀。平均幀處理速率為 1.23 幀/秒。這個速率取決于給定幀處理了多少邊界框。讓我們看一下我制作的數(shù)據(jù)框。
包含視頻聚類結(jié)果的 Pandas 數(shù)據(jù)幀。
數(shù)據(jù)框具有所需的結(jié)構(gòu)。可以看出,總共處理了 9,307 個邊界框。這意味著每秒可以處理 15.8 個邊界框。將刪除數(shù)據(jù)框中包含 NaN 數(shù)組的所有行(這些是包含誤報的數(shù)組)。
到目前為止處理的數(shù)據(jù)集中有281個誤報,這意味著球員對象的分類過程有97%的準確率,相當不錯!現(xiàn)在將嘗試使用我們數(shù)據(jù)幀中 RGB 列上的 K-Means 例程查看當前處理的 MP4 幀中的前五種顏色。
所有已處理的邊界框中最常見的顏色是什么?
認為在處理后的 MP4 文件中查看最常出現(xiàn)的顏色會很有趣。這個過程可以幫助識別與視頻片段中出現(xiàn)的球隊相關(guān)的球衣顏色,然后可以用來開發(fā)能夠區(qū)分不同球隊球員的算法程序。
由于有一個 pandas 數(shù)據(jù)框,其中包含每個邊界框中出現(xiàn)的最多、第二和第三主要顏色,因此我可以利用與之前介紹的聚類例程類似的聚類例程,從這些類別中的每一個中獲取最主要的顏色。此過程的結(jié)果如下所示:
目前所有球員邊界框中最常見的顏色。
如上圖所示,深灰色對應于總顏色的 29.32%,栗紅色對應于所有邊界框中的顏色的 27.60%。處理后的視頻中的球隊球衣是紅色和白色的。
由于陰影和照明的差異,這里觀察到的前兩種顏色準確地描繪了球隊球衣,隨后可以用于球隊分類。
處理整個比賽
在下面顯示的例程已在單個 MP4 文件上進行了演示。然而,這個 MP4 文件對應于幾分鐘比賽時間的鏡頭。
話雖如此,將代碼例程開發(fā)得相當模塊化,因此,它可以通過以下例程連接從每個 MP4 文件獲得的結(jié)果來輕松地用于處理整個比賽過程:
def getJerseyColorsFromGame(jsonPath,MP4Path,whichVideo,nClusters):
mp4_list = getListOfFiles(MP4Path , ".mp4")
n_mp4 = len(mp4_list)
df_list = []
for vid in range(n_mp4):
jerseyColor_df = getJerseyColorsFromMP4(jsonPath,MP4Path,vid,nClusters)
df_list.a(chǎn)ppend(jerseyColor_df)
allJerseyColors = pd.concat(df_list)
return allJerseyColors
用于快速可視化聚類結(jié)果的 GUI
pandas 數(shù)據(jù)框整齊地存儲了聚類結(jié)果。然而,所有這些數(shù)字可能有點難以理解。因此,我編寫了一個 GUI,該 GUI 將使用軌跡欄在我的 pandas 數(shù)據(jù)框的行中移動,并沿著三種最主要的顏色繪制裁剪的球員邊界框。它將根據(jù)聚類算法確定它們的十六進制代碼。
下面顯示了此代碼以及運行中的 GUI 演示。
#Make image bigger
def makeBigger(img):
dim = (300, 200) #(width, height)
# resize image
resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
return resized
# empty function called when trackbar moves
def emptyFunction():
pass
#Panel to cycle through player bounding boxes and 2 dominant colors in each BB
def main(df):
# blackwindow having 3 color chanels
windowName ="Open CV Color Palette"
# window name
cv2.namedWindow(windowName)
# Define trackbar
rows = df.shape[0]-1
cv2.createTrackbar('BB ID', windowName, 0, rows, emptyFunction)
#previousTrackbarValue = -1 # Set this to -1 so the threshold will be applied and the image displayed the first time through the loop
# Used to open the window until press ESC key
while(True):
if cv2.waitKey(1) == 27:
break
# Which row to look at in dataframe?
bbID = cv2.getTrackbarPos('BB ID', windowName)
print(bbID)
fName = df.iloc[bbID]['File Name']
print(fName)
bb = cv2.imread(fName)
bb = makeBigger(bb)
bbsize = bb.shape
image1 = np.zeros((bbsize[0], bbsize[1], 3), np.uint8)
image2 = np.zeros((bbsize[0], bbsize[1], 3), np.uint8)
image3 = np.zeros((bbsize[0], bbsize[1], 3), np.uint8)
# values of blue, green, red extracted from the dataframe
hex_string1 = df.iloc[bbID]['Jersey Color 1']
hex_string2 = df.iloc[bbID]['Jersey Color 2']
hex_string3 = df.iloc[bbID]['Jersey Color 3']
rgb1 = hex_to_rgb(hex_string1)
blue1 = rgb1[2]
green1 = rgb1[1]
red1 = rgb1[0]
rgb2 = hex_to_rgb(hex_string2)
blue2 = rgb2[2]
green2 = rgb2[1]
red2 = rgb2[0]
rgb3 = hex_to_rgb(hex_string3)
blue3 = rgb3[2]
green3 = rgb3[1]
red3 = rgb3[0]
# font
font = cv2.FONT_HERSHEY_SIMPLEX
# org
org = (75, 50)
# fontScale
fontScale = 1
# Blue color in BGR
color = (255, 0, 0)
# Line thickness of 2 px
thickness = 2
image1[:] = [blue1, green1, red1]
image2[:] = [blue2, green2, red2]
image3[:] = [blue3, green3, red3]
# Using cv2.putText() method
image1 = cv2.putText(image1, hex_string1, org, font, fontScale, color, thickness, cv2.LINE_AA)
image2 = cv2.putText(image2, hex_string2, org, font, fontScale, color, thickness, cv2.LINE_AA)
image3 = cv2.putText(image3, hex_string3, org, font, fontScale, color, thickness, cv2.LINE_AA)
# concatenate image Vertically
verti = np.concatenate((bb, image1, image2, image3), axis=0)
cv2.imshow(windowName, verti)
cv2.destroyAllWindows()
展示聚類結(jié)果的 GUI 演示。
結(jié)論
上面顯示的工作演示了如何使用 K-Means 聚類算法從足球比賽視頻片段中提取球衣顏色。在這個過程中,某些方面還有待改進。例如,可以嘗試通過視頻的多處理或批處理來提高代碼效率。
此外,可以通過在處理誤報(即裁判、非球員對象)時包含更好的錯誤處理來改進例程。聚類過程結(jié)果與預期輸出一致(即,主要顏色是紅色和白色,而球隊球衣顏色是紅色和白色)。
原文標題 : 使用 Python 從視頻片段中確定足球球員球衣的顏色
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
即日-10.29立即報名>> 2024德州儀器嵌入式技術(shù)創(chuàng)新發(fā)展研討會
-
10月31日立即下載>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月14日立即報名>> 2024工程師系列—工業(yè)電子技術(shù)在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
推薦專題
- 1 Intel宣布40年來最重大轉(zhuǎn)型:年底前裁員15000人、拋掉2/3房產(chǎn)
- 2 因美封殺TikTok,字節(jié)股價骨折!估值僅Meta1/5
- 3 宏山激光重磅發(fā)布行業(yè)解決方案,助力智能制造產(chǎn)業(yè)新飛躍
- 4 國產(chǎn)AI芯片公司破產(chǎn)!白菜價拍賣
- 5 具身智能火了,但規(guī)模落地還需時間
- 6 國產(chǎn)英偉達們,抓緊沖刺A股
- 7 三次錯失風口!OpenAI前員工殺回AI編程賽道,老東家捧金相助
- 8 英特爾賦能智慧醫(yī)療,共創(chuàng)數(shù)字化未來
- 9 英偉達的麻煩在后頭?
- 10 將“網(wǎng)紅”變成“商品”,AI“爆改”實力拉滿
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市