使用 Python 從視頻片段中確定足球球員球衣的顏色
使用 K-Means 聚類來識別球員球衣顏色
足球是世界上最受歡迎的運(yùn)動(dòng)。在洪都拉斯,足球能夠吸引大眾的注意力,并在 90 分鐘內(nèi)讓人群陷入情緒的漩渦。
多年來,我們看到各種技術(shù)被實(shí)施,以獲取有關(guān)比賽內(nèi)事件和球員表現(xiàn)的各種統(tǒng)計(jì)數(shù)據(jù)和信息。
通常,不僅為足球,而且為許多其他運(yùn)動(dòng)開發(fā)/實(shí)施的最有趣的技術(shù)應(yīng)用程序之一是計(jì)算機(jī)視覺。計(jì)算機(jī)視覺 (CV) 是有關(guān)開發(fā)能夠理解圖像或視頻等視覺數(shù)據(jù)的算法和/或人工智能的領(lǐng)域。CV 非常強(qiáng)大,在 Instagram 過濾器、自動(dòng)駕駛汽車、MRI 重建、癌癥檢測等許多應(yīng)用中都很常見。
在這個(gè)項(xiàng)目中,我們在不同的足球比賽中拍攝了一系列視頻片段,并使用 K-Means 聚類算法確定了球員球衣顏色的顏色。
本文將詳細(xì)介紹實(shí)現(xiàn)該目標(biāo)的過程。這里開發(fā)的例程將視頻片段作為輸入,并生成一個(gè)包含聚類過程結(jié)果的 pandas 數(shù)據(jù)幀作為輸出。
這個(gè)項(xiàng)目需要執(zhí)行數(shù)據(jù)清理、聚類、圖像/視頻處理、圖像中對象的基本分類、讀取 JSON 文件以及各種 pandas/numpy 數(shù)組/列表操作。
本文目錄:
· 圖像處理基礎(chǔ)
· 從圖像中提取顏色
· 從視頻文件中提取幀
· 從JSON文件中提取播放器邊界框
· 實(shí)現(xiàn)K-Means聚類確定球員球衣顏色
· 制作用于快速可視化聚類結(jié)果的GUI
· 結(jié)論
讓我們開始吧!
圖像/視頻處理基礎(chǔ)
本節(jié)將介紹對本項(xiàng)目很重要的圖像和視頻處理/操作的基礎(chǔ)知識。
使用足球歷史上我最喜歡的時(shí)刻之一作為參考圖像來嘗試各種可用的處理技術(shù)。那一刻是羅納爾迪尼奧在 2005 年 11 月 19 日效力于巴塞羅那足球俱樂部時(shí),對陣皇家馬德里的精彩進(jìn)球,如下圖所示。
2005 年 11 月 19 日,羅納爾迪尼奧對皇家馬德里的進(jìn)球。
使用 OpenCV 加載圖像
需要做的第一件事是將圖像加載到筆記本中。如果你將圖像保存在計(jì)算機(jī)上,則可以簡單地使用cv2.imread函數(shù)。但是,對于在這部分工作中使用的圖像,是通過 URL 獲取的。然后,加載圖像需要我們:
1. 將我們的 URL 傳入urllib.request.urlopen
2. 從 URL 中的圖像創(chuàng)建一個(gè) numpy 數(shù)組
3. cv2.imdecode用于從內(nèi)存緩存中讀取圖像數(shù)據(jù),并將其轉(zhuǎn)換為圖像格式。
4. 由于cv2.imdecode默認(rèn)以 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 空間中加載的圖像。
如你所見,加載的此圖像中的顏色與原始圖像中看到的顏色不匹配。這是因?yàn)?OpenCV 在 BGR 顏色空間中默認(rèn)加載的圖像。
不過問題不大,因?yàn)榍袚Q到 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ù)具有最簡單的實(shí)現(xiàn),因?yàn)樗枰闹皇且D(zhuǎn)的圖像,以及我們要旋轉(zhuǎn)圖像的角度。
除此之外,此功能確保顯示的旋轉(zhuǎn)圖像不會(huì)被裁剪并完全包含在邊界內(nèi)。其他方法需要首先構(gòu)建旋轉(zhuǎn)矩陣,然后應(yīng)用旋轉(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 加載圖像時(shí),圖像被加載為 numpy 數(shù)組。然后,要裁剪圖像,我們可以簡單地使用 numpy 切片來裁剪內(nèi)容。
我們有多種裁剪的方法。將在這里展示一個(gè)簡單的示例,我們可以按不同的高度和寬度百分比裁剪圖像。通過定義感興趣區(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)整圖像大小。盡管圖像看起來相同,但可以看出,當(dāng)我們調(diào)整圖像大小時(shí),圖像的大小(高度和寬度)會(huì)發(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)整圖像的亮度/對比度。這是一個(gè)稱為混合的過程。此函數(shù)使用以下轉(zhuǎn)換對圖像進(jìn)行這些調(diào)整:
result = αsrc1 + βsrc2 + γ
在上面的等式中,通過將α值應(yīng)用于源圖像、將β值應(yīng)用于其他圖像(它可以是相同的源圖像)并將其值增加來修改混合圖像γ。
混合效果如下圖所示。
第一行圖顯示了α在保持其他兩個(gè)參數(shù)不變的情況下變化的效果(α從左到右遞減)。
第二行圖顯示了β在保持其他兩個(gè)參數(shù)不變的情況下變化的效果(β從左到右增加)。
第三行圖顯示了γ在保持其他兩個(gè)參數(shù)不變的情況下變化的效果(γ從左到右增加)。
· 減小α使圖像變暗。
· 增加β使圖像具有更強(qiáng)的對比度。
· 減小γ使圖像柔化。
#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 中更改圖像的亮度和對比度。
更改圖像的色彩空間
圖像處理中使用了多種顏色空間,可以促進(jìn)各種任務(wù),例如邊緣檢測、顏色檢測和應(yīng)用蒙版等等。
使用 OpenCV 通過cvtColor函數(shù)可以很容易地在顏色空間之間進(jìn)行轉(zhuǎn)換
下面列出了一些常見的色彩空間:
· RGB -> 許多圖像最初都是使用這種格式編碼的
· HSV -> 提供對顏色色調(diào)的更好控制
· 灰色 -> 使許多圖像處理方法更準(zhǔn)確
改變顏色空間的一些示例如下所示:
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 中更改顏色空間。
圖像模糊
當(dāng)試圖檢測邊緣(即描繪從一組像素到另一組像素的過渡的線條)時(shí),模糊是一項(xiàng)重要的操作,因?yàn)樗箤ο筮吔缰g的過渡更加平滑。例如,這可用于將對象與背景分離。
為這個(gè)項(xiàng)目研究了四個(gè)類別:
· 平均模糊 -> 快速但可能無法保留對象邊緣
· 高斯模糊 -> 比平均模糊慢,但邊緣保留得更好
· 中值過濾 -> 對異常值具有魯棒性
· 雙邊過濾 -> 比上述方法慢得多。更多參數(shù)(更可調(diào))。
使用不同模糊方法的效果如下圖所示。
第一行圖顯示了使用平均模糊同時(shí)從左到右增加內(nèi)核大小的效果。
第二行圖顯示了使用高斯模糊同時(shí)從左到右增加內(nèi)核大小的效果。
第三行圖顯示了使用中值模糊同時(shí)從左到右增加內(nèi)核大小的效果。
第四行圖顯示了使用雙邊模糊同時(shí)從左到右增加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 進(jìn)行邊緣檢測。
掩蓋圖像中的顏色(應(yīng)用蒙版)
通常,人們可能只想在圖像中顯示特定的顏色。這可以通過蒙版來實(shí)現(xiàn)。
OpenCV 中的inRange功能允許在 HSV 空間中完成此操作。
下面顯示的圖像(從左到右)分別是未應(yīng)用蒙版、蒙版綠色、紅色和藍(lán)色的結(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 圖像。
從圖像中提取顏色
在這一點(diǎn)上,已經(jīng)介紹了許多基本的處理操作,這些操作應(yīng)該足以從圖像中確定球員球衣顏色。為了確定顏色,嘗試了以下方法:
· 在單個(gè)像素處提取顏色
· 通過逐像素平均提取顏色
· 使用 K-Means 聚類獲得圖像中的 k-colors
在單個(gè)像素處提取顏色
通過將像素的 x,y 坐標(biāo)提供給圖像數(shù)組,讀取該像素的 RGB 通道的結(jié)果,并將這些 RGB 通道分配到一個(gè)數(shù)組中,可以輕松地提取單個(gè)像素的顏色。
寫了一個(gè)小函數(shù)來處理下圖中的各種像素。下面顯示了 17 個(gè)不同像素的結(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)在我們可以提取單個(gè)像素的顏色,我們可以擴(kuò)展該方法來確定圖像的平均顏色。將 x, y 坐標(biāo)傳遞給我們的圖像數(shù)組會(huì)返回一個(gè)像素的 RGB 元組。
然后,通過在每個(gè)像素處添加元組中每個(gè)元素的值,我們可以獲得與每個(gè) RGB 通道相關(guān)的“總計(jì)數(shù)”。最后,我們可以將每個(gè) RGB 顏色通道中的計(jì)數(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)
這個(gè)過程的結(jié)果如下所示,其中平均顏色變成了一個(gè) HEX 值為#787561的灰綠色,通過對圖像的視覺檢查,這看起來是合理的。但是,我們可以改善這一點(diǎn)嗎?
圖像中最常見的用戶顏色由平均確定。
通過 K-Means 聚類提取主色
K-Means 聚類算法可以進(jìn)一步改進(jìn)球員球衣顏色檢測程序。該例程將允許我們通過指定例程應(yīng)使用的簇?cái)?shù)k來提取圖像中的幾種“主要顏色”。如果知道數(shù)據(jù)應(yīng)該屬于多少個(gè)集群,則可以先驗(yàn)地確定k的值。
否則,確定k值的常用方法是通過肘部法,如下所示。圖表的拐點(diǎn)(又名肘部)是應(yīng)該使用的 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_)
進(jìn)行肘部法的結(jié)果。
在圖像上運(yùn)行 k-means 聚類
確定k應(yīng)該是3后,可以編寫一個(gè)小程序來拍攝圖像并確定其 k 主導(dǎo)顏色。
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確定的頂部顏色是綠色,在目視檢查時(shí),考慮到“綠色區(qū)域”的普遍存在,它看起來是正確的。
#Call K-Means function with K = 4
nClusters = 4
rgb_array = KMeansTest(rgb_img, nClusters)
plotKMeansResult(nClusters,rgb_array)
從 K-Means 聚類確定的圖像中的前四種顏色。圖像中的色彩流行度從左到右遞減。
使用k=4確定的頂部顏色也是綠色。然而,它是一個(gè)更明亮的陰影。
#Call K-Means function with K = 10
nClusters = 10
rgb_array = KMeansTest(rgb_img, nClusters)
plotKMeansResult(nClusters,rgb_array)
從 K-Means 聚類確定的圖像中的前 10 種顏色。圖像中的色彩流行度從左到右遞減。
使用k=10確定的頂部顏色也是綠色。但是,它比前兩個(gè)示例要亮得多。從k=10的使用可以看出,可以通過使用更多的簇來獲得更高的顏色特異性。
要點(diǎn)是 k-means 例程可以準(zhǔn)確地檢測顏色并提供圖像中最常出現(xiàn)的顏色。
識別圖像中的人
在開始處理視頻片段之前,還對另一件事感興趣。想檢查圖像中球員/人的分類和/或識別。這些對象的分類不是作業(yè)的一部分,但想簡要探討一下以滿足我的好奇心。
OpenCV 中的 HOG 包包含訓(xùn)練模型的數(shù)據(jù)庫,這些模型能夠檢測不同的對象,如貓、臉和人類。在以后的文章中,將展示為解決這個(gè)分類問題而構(gòu)建的神經(jīng)網(wǎng)絡(luò)模型。但現(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í)間研究檢測器的參數(shù),并沒有得到更好的結(jié)果。嘗試使用DefaultPeopleDetector和Haar 級聯(lián)分類器haarcascade_fullbody,但無法得到想要的結(jié)果。
盡管檢測球員本身不是項(xiàng)目的一部分(得到了一個(gè)包含球員邊界框坐標(biāo)的 JSON 文件),但仍然想確保成功使用 HOG 檢測器。
在下面嘗試了一個(gè)不同的圖像,認(rèn)為它可以讓我成功檢測。在嘗試了幾分鐘的參數(shù)后,我找到了一個(gè)有效的組合!決定僅使用球員邊界框 (BB) 生成圖像,并將 K-means 例程應(yīng)用于該 BB 的內(nèi)容。結(jié)果如下所示:
使用 HOG 包檢測球員并提取包含球員的邊界框。
上圖中的前 4 種顏色是通過 K-Means 聚類確定的。
需要研究優(yōu)化/自動(dòng)化對象檢測器功能的參數(shù),但對到目前為止的進(jìn)展感到滿意。
處理視頻和提取幀
在熟悉了各種圖像處理和處理技術(shù)并了解如何實(shí)施 K-Means 以提取圖像中的主色后,我決定開始處理視頻片段,因?yàn)槲掖_信我有開發(fā)的基礎(chǔ),可以從圖像中確定球衣顏色。
部分文件涉及從兩個(gè)不同的攝像機(jī)拍攝的足球比賽的視頻片段。需要做的第一件事是獲取視頻文件,使用以下代碼:
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 文件的列表,可以從視頻中提取單個(gè)幀:
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)在可以可視化從兩個(gè)相機(jī)中提取的幀。幀 2500 如下所示:
分別從左右相機(jī)中提取幀。
想從視頻中提取的另一件事是其中的幀數(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 文件包含球員邊界框坐標(biāo)。需要做的第一件事是加載 JSON 文件。
JSON 文件與 MP4 文件相關(guān)聯(lián),因此必須確保在批處理所有文件時(shí),正確的 JSON 文件與正確的 MP4 文件配對。
為此,將首先從之前生成的文件名列表中刪除路徑和.json擴(kuò)展名,并將結(jié)果放入名為json_strip的列表中。
然后,將從先前生成的每個(gè)相機(jī)(LCAMERA和RCAMERA)的MP4文件列表中刪除路徑,并將結(jié)果分別放入兩個(gè)名為MP4_strip_LC和MP4_strap_RC的列表中。
最后,將使用這些列表從每臺攝像機(jī)中獲取與這些文件相關(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 文件并使用上面的框架獲取綁定框信息。為此,將為正在處理的視頻中的每個(gè)幀生成一個(gè)字典,其中包含每個(gè)球員邊界框(檢測)的坐標(biāo)。
#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)
上面的代碼為提供了當(dāng)前視頻中每一幀的邊界框。
接下來,將確定給定幀中有多少個(gè)邊界框。
#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ìn)行檢測。讓我們根據(jù) JSON 文件計(jì)算出視頻的檢測收集間隔。
#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 文件中提取當(dāng)前幀的所有邊界框坐標(biāo)。
#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 文件中提取特定邊界框的邊界框坐標(biāo)。
#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 幀。
來自左右攝像機(jī)的視頻素材的原始第一幀。
繪制了球員邊界框的幀如下所示。
來自帶有播放器邊界框的左右攝像機(jī)的視頻片段的幀。
最后,這是幀中每個(gè)球員的邊界框。
來自左側(cè)攝像頭的球員邊界框。
來自右側(cè)攝像機(jī)的球員邊界框。
到目前為止的方法允許我成功地提取球員邊界框。從一些邊界框可以看出一些東西。
首先,存在誤報(bào)的情況。此數(shù)據(jù)中的誤報(bào)意味著沒有球員的邊界框。這是未來需要解決的問題。
應(yīng)用 K-Means 聚類確定球員球衣顏色
現(xiàn)在數(shù)據(jù)形狀正確,F(xiàn)在,讓我們嘗試在邊界框上應(yīng)用 K-Means 聚類例程,看看會(huì)發(fā)生什么。將堅(jiān)持處理到目前為止我一直在使用的相同視頻和幀,以便可以專注于聚類本身。
聚類例程如下所示。該例程需要以下步驟:
1. 將圖像(球員邊界框)轉(zhuǎn)換為 HSV 顏色空間
2. 將圖像展平為一維陣列,以便于處理
3. 運(yùn)行 K 均值聚類
4. 確定圖像上每種顏色的百分比
5. 按降序?qū)@些顏色進(jìn)行排序并將它們放入一個(gè)數(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
在前四個(gè)邊界框上運(yùn)行此例程的結(jié)果如下所示:
顯示的邊界框上的聚類結(jié)果。
上述結(jié)果的主要內(nèi)容之一是綠色是所有邊界框中的主要顏色。
綠色色調(diào)主要來自田野中存在的草。這就是蒙版將發(fā)揮作用的地方。
將蒙版應(yīng)用于圖像數(shù)據(jù)
為了更好地處理邊界框中草場的存在,將使用蒙版。包括為構(gòu)成 HSV 顏色空間的三個(gè)值(即色調(diào)、飽和度、亮度)中的每一個(gè)設(shè)置低閾值和高閾值。如果一種顏色落在此閾值的范圍內(nèi),那么它將被屏蔽掉。
此外,添加了一些錯(cuò)誤處理,用于蒙版過程刪除了太多像素的情況。聚類例程要求要處理的圖像至少具有與聚類一樣多的唯一像素。因此,如果生成的蒙版圖像的尺寸低于所需的簇?cái)?shù),則該圖像將被忽略。這種情況很可能發(fā)生在邊界框只有一個(gè)字段的情況下。
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é)果。使用蒙版去除綠色對改進(jìn)顏色檢測程序有很大幫助!
應(yīng)用綠色蒙版后球員邊界框中的主要顏色。
移除邊界框的下半部分以專注于球衣數(shù)據(jù)
由于任務(wù)是僅確定球衣顏色,因此裁剪圖像的底部也有助于加強(qiáng)分析,因?yàn)槲覀兛梢詫W⒂诟匾膮^(qū)域并提高球員球衣顏色檢測的準(zhǔn)確性。這可以通過下面的代碼段簡單地完成。
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
應(yīng)用蒙版和裁剪后的聚類例程的結(jié)果如下所示:
蒙版字段和裁剪圖像底部后的主要顏色。
處理整個(gè) MP4 文件
讓我們嘗試處理每一幀,看看會(huì)發(fā)生什么!
到目前為止,已經(jīng)完成了所有例程并將它們放入下面的包裝函數(shù)中。
此包裝函數(shù)將 JSON 文件的路徑、mp4 文件的路徑、要處理的視頻以及 k-means 的聚類數(shù)作為輸入。
此函數(shù)的輸出是一個(gè) pandas 數(shù)據(jù)幀,其中包含當(dāng)前視頻每幀中每個(gè)邊界框的 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 幀/秒。這個(gè)速率取決于給定幀處理了多少邊界框。讓我們看一下我制作的數(shù)據(jù)框。
包含視頻聚類結(jié)果的 Pandas 數(shù)據(jù)幀。
數(shù)據(jù)框具有所需的結(jié)構(gòu)?梢钥闯觯偣蔡幚砹 9,307 個(gè)邊界框。這意味著每秒可以處理 15.8 個(gè)邊界框。將刪除數(shù)據(jù)框中包含 NaN 數(shù)組的所有行(這些是包含誤報(bào)的數(shù)組)。
到目前為止處理的數(shù)據(jù)集中有281個(gè)誤報(bào),這意味著球員對象的分類過程有97%的準(zhǔn)確率,相當(dāng)不錯(cuò)!現(xiàn)在將嘗試使用我們數(shù)據(jù)幀中 RGB 列上的 K-Means 例程查看當(dāng)前處理的 MP4 幀中的前五種顏色。
所有已處理的邊界框中最常見的顏色是什么?
認(rèn)為在處理后的 MP4 文件中查看最常出現(xiàn)的顏色會(huì)很有趣。這個(gè)過程可以幫助識別與視頻片段中出現(xiàn)的球隊(duì)相關(guān)的球衣顏色,然后可以用來開發(fā)能夠區(qū)分不同球隊(duì)球員的算法程序。
由于有一個(gè) pandas 數(shù)據(jù)框,其中包含每個(gè)邊界框中出現(xiàn)的最多、第二和第三主要顏色,因此我可以利用與之前介紹的聚類例程類似的聚類例程,從這些類別中的每一個(gè)中獲取最主要的顏色。此過程的結(jié)果如下所示:
目前所有球員邊界框中最常見的顏色。
如上圖所示,深灰色對應(yīng)于總顏色的 29.32%,栗紅色對應(yīng)于所有邊界框中的顏色的 27.60%。處理后的視頻中的球隊(duì)球衣是紅色和白色的。
由于陰影和照明的差異,這里觀察到的前兩種顏色準(zhǔn)確地描繪了球隊(duì)球衣,隨后可以用于球隊(duì)分類。
處理整個(gè)比賽
在下面顯示的例程已在單個(gè) MP4 文件上進(jìn)行了演示。然而,這個(gè) MP4 文件對應(yīng)于幾分鐘比賽時(shí)間的鏡頭。
話雖如此,將代碼例程開發(fā)得相當(dāng)模塊化,因此,它可以通過以下例程連接從每個(gè) MP4 文件獲得的結(jié)果來輕松地用于處理整個(gè)比賽過程:
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ù)字可能有點(diǎn)難以理解。因此,我編寫了一個(gè) GUI,該 GUI 將使用軌跡欄在我的 pandas 數(shù)據(jù)框的行中移動(dòng),并沿著三種最主要的顏色繪制裁剪的球員邊界框。它將根據(jù)聚類算法確定它們的十六進(jìn)制代碼。
下面顯示了此代碼以及運(yùn)行中的 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 聚類算法從足球比賽視頻片段中提取球衣顏色。在這個(gè)過程中,某些方面還有待改進(jìn)。例如,可以嘗試通過視頻的多處理或批處理來提高代碼效率。
此外,可以通過在處理誤報(bào)(即裁判、非球員對象)時(shí)包含更好的錯(cuò)誤處理來改進(jìn)例程。聚類過程結(jié)果與預(yù)期輸出一致(即,主要顏色是紅色和白色,而球隊(duì)球衣顏色是紅色和白色)。
原文標(biāo)題 : 使用 Python 從視頻片段中確定足球球員球衣的顏色

請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個(gè)字
最新活動(dòng)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評 >> 【評選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺
- 5 國產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開成長空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?