訂閱
糾錯
加入自媒體

使用 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:Users murcDocumentsGitHubopencvdatahaarcascadeshaarcascade_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 從視頻片段中確定足球球員球衣的顏色

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯
x
*文字標題:
*糾錯內(nèi)容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網(wǎng)安備 44030502002758號