Posted on

如何分割黏在一起的撲克牌

範例說明

本文為參考下面的文章:
Image Segmentation with Distance Transform and Watershed Algorithm

這篇文章是OpenCV官方網站上的一篇教程,介紹了如何使用distanceTransform函數進行圖像分割。在這篇教程中,作者首先介紹了distanceTransform函數的基本概念和用法,然後通過一個實例演示了如何使用distanceTransform函數對圖像進行分割。

範例程式碼

以下為程式範例

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\
    Sample code showing how to segment overlapping objects using Laplacian filtering, \
    in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='cards.png')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
    print('Could not open or find the image:', args.input)
    exit(0)
# Show source image
cv.imshow('Source Image', src)
src[np.all(src == 255, axis=2)] = 0
# Show output image
cv.imshow('Black Background Image', src)
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
# do the laplacian filtering as it is
# well, we need to convert everything in something more deeper then CV_8U
# because the kernel has some negative values,
# and we can expect in general to have a Laplacian image with negative values
# BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
# so the possible negative number will be truncated
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel)
sharp = np.float32(src)
imgResult = sharp - imgLaplacian
# convert back to 8bits gray scale
imgResult = np.clip(imgResult, 0, 255)
imgResult = imgResult.astype('uint8')
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)
#cv.imshow('Laplace Filtered Image', imgLaplacian)
cv.imshow('New Sharped Image', imgResult)
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY)
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('Binary Image', bw)
dist = cv.distanceTransform(bw, cv.DIST_L2, 3)
# Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)
_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY)
# Dilate a bit the dist image
kernel1 = np.ones((3,3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)
dist_8u = dist.astype('uint8')
# Find total markers
_, contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# Create the marker image for the watershed algorithm
markers = np.zeros(dist.shape, dtype=np.int32)
# Draw the foreground markers
for i in range(len(contours)):
    cv.drawContours(markers, contours, i, (i+1), -1)
# Draw the background marker
cv.circle(markers, (5,5), 3, (255,255,255), -1)
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)
cv.watershed(imgResult, markers)
#mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
# uncomment this if you want to see how the mark
# image looks like at that point
#cv.imshow('Markers_v2', mark)
# Generate random colors
colors = []
for contour in contours:
    colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i,j]
        if index > 0 and index <= len(contours):
            dst[i,j,:] = colors[index-1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.waitKey()

distanceTransform

distanceTransform函數是OpenCV中的一個函數,用於計算圖像中每個非零點到最近背景像素的距離。distanceTransform函數的第二個Mat矩陣參數dst保存了每個點與最近的零點的距離信息,圖像上越亮的點,代表了離零點的距離越遠。在這篇文章中,作者通過一個實例演示了如何使用distanceTransform函數對圖像進行分割。

在這個實例中,作者首先讀取了一張灰度圖像,然後使用threshold函數對圖像進行二值化處理。接著,作者使用distanceTransform函數計算了圖像中每個非零點到最近背景像素的距離,並將結果保存在了一個Mat矩陣中。最後,作者使用threshold函數對Mat矩陣進行二值化處理,得到了一張分割後的圖像。

需要注意的是,在使用distanceTransform函數時,需要先將圖像進行二值化處理。此外,在計算距離時,可以選擇歐氏距離、L1距離或L-infinity距離等不同的計算方式。

處理的過程圖片





其他參考資料

OpenCV C++/Obj-C: Advanced square detection

Posted on

拆分相黏的正方形

原始圖片

在寫純OpenCV的圖像辨識上,最困難的是當要找尋的目標的邊界因為模糊或相黏,而無法抓出正確的邊界的狀況
因為一般使用OpenCV做圖像辨識,我們都會需要先抓到例如說畫面中的某種色塊、再找尋某種符合某條件的形狀之類的
例如以修圖軟體而言,可能會需要先抓取膚色,然後轉換膚色的灰度階層取得面部的高低起伏,再根據灰度階層去抓取符合某種形狀的高低(如鼻子)或顏色差(如嘴巴、眼睛)

也因此,抓取正確的形狀在純粹的圖像辨識(沒有機器學習)的狀況下非常重要,而拆分相黏的形狀(如手放在臉前面),仍然要正確的辨識目標物件,也成了圖像辨識的一大挑戰
關於這一系列的其他文章,請見:

以下為這次我們要挑戰的目標,就是將這兩個黏在一起的正方形拆分為兩個正方形

解決問題的思考方向

首先我先參考官網的分水嶺演算法介紹:Image Segmentation with Watershed Algorithm
這邊的狀況和我們的需求很類似,都是將相黏的物件拆分開來

下面是在stackoverflow裡一位大大分享的他使用分水嶺演算法的範例程式:

from scipy.ndimage import label
import cv2
import numpy
def segment_on_dt(img):
    dt = cv2.distanceTransform(img, cv2.DIST_L2, 3) # L2 norm, 3x3 mask
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(numpy.uint8)
    dt = cv2.threshold(dt, 100, 255, cv2.THRESH_BINARY)[1]
    lbl, ncc = label(dt)

    lbl[img == 0] = lbl.max() + 1
    lbl = lbl.astype(numpy.int32)
    cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), lbl)
    lbl[lbl == -1] = 0

在這篇文章裡有幾個函數需要我們去理解:

cv.distanceTransform

OpenCV的distanceTransform是一個圖像處理功能,可以計算圖像中每個像素到最近的零值像素之間的歐幾里德距離。distanceTransform功能可以在圖像分割、形狀檢測、物體識別等應用中使用。
在OpenCV中,distanceTransform有三種不同的實現方式:cv2.DIST_L1、cv2.DIST_L2和cv2.DIST_C。cv2.DIST_L1使用曼哈頓距離,cv2.DIST_L2使用歐幾里德距離,而cv2.DIST_C使用切比雪夫距離。
使用distanceTransform功能需要先將圖像二值化,然後計算圖像中每個像素到最近的零值像素之間的距離。distanceTransform返回的結果是一個浮點型的圖像,每個像素值表示該像素到最近的零值像素之間的距離。

以下是distanceTransform的Python程式碼示例:

import cv2
import numpy as np

img = cv2.imread('image.jpg', 0)
ret,thresh = cv2.threshold(img,127,255,0)
dist_transform = cv2.distanceTransform(thresh,cv2.DIST_L2,5)

以下為對上圖做cv.distanceTransform的結果

cv.threshold

將距離變換圖像dt進行歸一化處理,將其轉換成0-255之間的整數型圖像,並使用threshold函數將其二值化,生成一個二值化圖像dt。這個二值化圖像dt中的像素值只有0和255兩種,用來表示圖像中物體和背景之間的分界線。
在這邊我使用了0.9*dist_transform.max()來做為閥值,確認兩個方形之間可以不相連

ret, sure_fg = cv.threshold(dist_transform,0.9*dist_transform.max(),255,0)

結果如下:

cv2.connectedComponents

在圖像處理和計算機視覺中,常常需要將圖像分割成多個不同的區域,然後對每個區域進行不同的分析和處理。圖像分割的第一步就是對圖像進行連通區域標記,將相連的像素點標記為同一個區域,以便後續處理。

程式碼使用OpenCV中的label函數對二值化圖像dt進行連通區域標記,生成一個標記圖像lbl和連通區域數量ncc。標記圖像lbl中的每個像素點都標記了其所屬的連通區域編號。

OpenCV中提供了幾個函數可以實現連通區域標記,其中最常用的是cv2.connectedComponents和cv2.connectedComponentsWithStats函數,這些函數會將每個連通區域分配一個唯一的標籤(編號),並返回每個區域的一些統計信息,如面積、重心等。

scipy.ndimage.labelcv2.connectedComponents都是對二值化圖像中的連通區域進行標記的函數,但在實現和用法上有所不同。

scipy.ndimage.label是Scipy中的一個函數,用於對二值化圖像進行連通區域標記,它的使用方式如下:

from scipy.ndimage import label
labels, num_features = label(binary_image)

其中,binary_image是二值化的圖像,labels是與原始圖像大小相同的數組,其中每個像素點都標記了其所屬的連通區域編號,num_features是圖像中連通區域的數量。

cv2.connectedComponents是OpenCV中的一個函數,用於對二值化圖像進行連通區域標記,它的使用方式如下:

num_labels, labels = cv2.connectedComponents(binary_image)

binary_image是二值化的圖像,num_labels是圖像中連通區域的數量,labels是與原始圖像大小相同的數組,其中每個像素點都標記了其所屬的連通區域編號。

兩個函數的返回值不同,scipy.ndimage.label返回的是每個像素點所屬的連通區域編號,而cv2.connectedComponents返回的是每個連通區域的編號。另外,cv2.connectedComponents還可以通過修改設置參數來指定標記的種類,例如指定為4或8連通等。

cv2.watershed

程式碼將標記圖像lbl轉換成整數型數組,並使用OpenCV中的watershed函數進行分水嶺分割,生成一個分割圖像lbl。分割圖像lbl中的像素點表示原始圖像中的每個像素點所屬的區域編號。

cv2.watershed() 是 OpenCV 中的一種分割算法,通常用於分割圖像中的目標物體。

在使用該函數時,需要先對輸入的圖像進行預處理,以便生成一組用於分割的初始標記。這通常可以通過在原始圖像中標記前景和背景像素來實現。然後,可以將這些標記傳遞給 cv2.watershed() 函數進行分割。該函數會根據標記和圖像的梯度信息來確定目標物體的邊界,將其分割為不同的區域。

函數的語法如下:

markers = cv2.watershed(img, markers)

img 是輸入圖像,markers 是與輸入圖像相同大小的標記矩陣。在函數執行完畢後,markers 矩陣中每個像素的值將被設置為其所屬的分割區域的標記值。
需要注意的是,cv2.watershed() 函數是一個原地操作,即它會修改傳遞給它的標記矩陣,而不是返回一個新的矩陣。因此,在調用該函數之前,最好複製一份原始標記矩陣以備份。
cv2.watershed() 函數是一種基於圖像分水嶺的分割算法,它可以對灰度圖像進行分割,將圖像中的前景和背景分開。該算法的分割結果是基於圖像梯度的變化來進行分割的,因此不能直接實現直線分割。

# 應用watershed算法進行圖像分割
    markers = cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), markers)
    # 根據標記將圖像分成不同的部分
    img[markers == -1] = 0

結果如下:

完整用法範例

import cv2
import numpy as np

# 創建一個黑白圖像
img = np.zeros((500, 500), dtype=np.uint8)
cv2.rectangle(img, (100, 100), (200, 200), 255, -1)
cv2.rectangle(img, (150, 150), (250, 250), 255, -1)

# 求出距離變換圖像
dt = cv2.distanceTransform(img, cv2.DIST_L2, 3)
dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8)
# 閾值分割得到前景和背景
ret, thresh = cv2.threshold(dt, 0.8*dt.max(),255,0)
thresh = cv2.dilate(thresh, None, iterations=4)
unknown = cv2.subtract(img,thresh)
# 得到標記圖像,每個區域用不同的正整數標記
ret, markers = cv2.connectedComponents(thresh)
markers = markers+1
markers[unknown==255] = 0
# 應用watershed算法進行圖像分割
markers = cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), markers)
# 根據標記將圖像分成不同的部分
img[markers == -1] = 0

# 顯示結果
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Posted on 1 Comment

如何檢測畫面中可能的正方形

最簡單-使用findContours

OpenCV 中有一個名為 findContours 的函數,可以用來查找圖像中的輪廓。一旦你找到了圖像中的輪廓,你可以使用 approxPolyDP 函數來近似地計算輪廓的形狀。如果你要查找正方形,你可以在這些形狀中尋找具有 4 個頂點的多邊形,這些多邊形應該有相近的邊長和角度。如果你要檢測的正方形不是水平的或垂直的,你可能需要使用角度信息來確定它的方向。

import cv2
# 读入图像
img = cv2.imread('square.jpg')

# 将图像转为灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用 Canny 边缘检测
edges = cv2.Canny(gray, 50, 150)

# 寻找轮廓
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 遍历轮廓
for contour in contours:
    # 近似计算轮廓
    approx = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)
    # 如果是 4 个顶点的多边形,且边长和角度相近,则认为是正方形
    if len(approx) == 4 and cv2.isContourConvex(app

當我們可以取得相黏物件邊緣時 – 分水嶺演算法

分水嶺演算法-偵測相連區域形狀

當形狀邊緣不清楚時-使用霍夫找線

使用霍夫變換檢測直線。因為正方形的四條邊是直線,因此可以通過檢測這四條直線來確定正方形。

具體來說,可以使用 OpenCV 中的 HoughLinesP 函數來檢測直線。該函數會返回一組檢測到的線段,這些線段可以是任意方向和長度的線段,因此我們需要對這些線段進行篩選,只保留長度、方向和相對位置都符合要求的線段。接著,我們可以將這些線段按照一定的規則組合成四條邊,從而確定正方形。

以下是一個示例代碼,演示如何使用霍夫變換檢測正方形:

import cv2
import numpy as np

# 读入图像
img = cv2.imread('square.jpg')

# 将图像转为灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用 Canny 边缘检测
edges = cv2.Canny(gray, 50, 150)

# 使用霍夫变换检测直线
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=10)

# 筛选直线
filtered_lines = []
for line in lines:
    x1, y1, x2, y2 = line[0]
    length = np.sqrt((x2-x1)**2 + (y2-y1)**2)
    angle = np.arctan2(y2-y1, x2-x1)
    # 保留长度在一定范围内的直线
    if 100 < length < 200:
        filtered_lines.append((length, angle, line))

# 组合直线
sides = []
for i, (length1, angle1, line1) in enumerate(filtered_lines):
    for j, (length2, angle2, line2) in enumerate(filtered_lines[i+1:], i+1):
        angle_diff = np.abs(angle1 - angle2)
        # 保留角度相近的直线
        if angle_diff < np.pi/4:
            # 计算两条直线的中心点
            x1, y1, x2, y2 = line1
            cx1, cy1 = (x1+x2)/2, (y1+y2)/2
            x1, y1, x2, y2 = line2
            cx2, cy2 = (x1+x2)/2, (y1+y2)/2
            center_diff = np.sqrt((cx2-cx1)**2 + (cy2-cy1)**2)
            # 保留中心点距离相近的直线
            if center_diff < 20:
                # 将两条直线组合成一条边
                sides.append((length1+length2, angle1, angle2, line1, line2))

# 按照长度排序,取前四条直线作为正方形的四条边
sides = sorted(sides
取前四条直线作为正方形的四条边
sides = sorted(sides, reverse=True)[:4]

计算正方形的四个顶点
corners = []
for i, (length1, angle1, angle2, line1, line2) in enumerate(sides):
for j, (length3, angle3, angle4, line3, line4) in enumerate(sides[i+1:], i+1):
# 计算两条直线的交点
x1, y1, x2, y2 = line1
cx1, cy1 = (x1+x2)/2, (y1+y2)/2
x1, y1, x2, y2 = line3
cx2, cy2 = (x1+x2)/2, (y1+y2)/2
k1 = np.tan(angle1)
k2 = np.tan(angle3)
x = (cy1 - cy2 + k2cx2 - k1cx1) / (k2 - k1)
y = k1*(x - cx1) + cy1
corners.append((x, y))

绘制正方形
for i in range(4):
x1, y1 = corners[i]
x2, y2 = corners[(i+1)%4]
cv2.line(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 3)

显示结果
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

基於角點的角點檢測

哈里斯角點檢測器簡介

Harris Corner Detector 是一種角點檢測算子,常用於計算機視覺算法中以提取角點並推斷圖像的特徵。它由 Chris Harris 和 Mike Stephens 於 1988 年在 Moravec 的角檢測器改進後首次提出。Harris 角點檢測器相比之前的角點檢測器,直接考慮了角點分數的差異,直接參考方向,而不是每 45 度角使用 shifting patches,並被證明在區分邊緣和邊緣方面更準確。角落。從那以後,它被改進並被許多算法採用,為後續應用預處理圖像。

Posted on 2 Comments

分水嶺演算法-偵測相連區域形狀

官方教學

Image Segmentation with Watershed Algorithm
官方的範例是一群黏在一起的硬幣

分割出黏在一起的長方形

這篇文章是在討論如何分割出一群黏在一起的長方形

給定一個二值圖像,我們可以應用距離變換 (DT) 並從中獲得分水嶺的標記。理想情況下,會有一個現成的函數來查找區域最小值/最大值,但由於它不存在,我們可以對如何設置 DT 閾值做出一個不錯的猜測。基於標記,我們可以使用 Watershed 進行分割,問題就解決了。現在您可以擔心區分矩形組件和非矩形組件了。

OpenCV的distanceTransform是一個圖像處理功能,可以計算圖像中每個像素到最近的零值像素之間的歐幾里德距離。distanceTransform功能可以在圖像分割、形狀檢測、物體識別等應用中使用。

在OpenCV中,distanceTransform有三種不同的實現方式:cv2.DIST_L1、cv2.DIST_L2和cv2.DIST_C。cv2.DIST_L1使用曼哈頓距離,cv2.DIST_L2使用歐幾里德距離,而cv2.DIST_C使用切比雪夫距離。

曼哈頓距離也稱為城市區塊距離或L1距離。它是兩點之間水平和垂直距離的總和。如果p1和p2是兩個二維坐標點,則曼哈頓距離可以通過以下公式計算:

d(p1, p2) = |p1.x – p2.x| + |p1.y – p2.y|

歐幾里德距離是兩個點之間的直線距離。如果p1和p2是兩個二維坐標點,則歐幾里德距離可以通過以下公式計算:

d(p1, p2) = sqrt((p1.x – p2.x)^2 + (p1.y – p2.y)^2)

切比雪夫距離是兩個點之間在所有方向上的最大距離。如果p1和p2是兩個二維坐標點,則切比雪夫距離可以通過以下公式計算:

d(p1, p2) = max(|p1.x – p2.x|, |p1.y – p2.y|)

import sys
import cv2
import numpy
import random
from scipy.ndimage import label

def segment_on_dt(img):
    dt = cv2.distanceTransform(img, 2, 3) # L2 norm, 3x3 mask
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(numpy.uint8)
    dt = cv2.threshold(dt, 100, 255, cv2.THRESH_BINARY)[1]
    lbl, ncc = label(dt)

    lbl[img == 0] = lbl.max() + 1
    lbl = lbl.astype(numpy.int32)
    cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), lbl)
    lbl[lbl == -1] = 0
    return lbl


img = cv2.cvtColor(cv2.imread(sys.argv[1]), cv2.COLOR_BGR2GRAY)
img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)[1]
img = 255 - img # White: objects; Black: background

ws_result = segment_on_dt(img)
# Colorize
height, width = ws_result.shape
ws_color = numpy.zeros((height, width, 3), dtype=numpy.uint8)
lbl, ncc = label(ws_result)
for l in xrange(1, ncc + 1):
    a, b = numpy.nonzero(lbl == l)
    if img[a[0], b[0]] == 0: # Do not color background.
        continue
    rgb = [random.randint(0, 255) for _ in xrange(3)]
    ws_color[lbl == l] = tuple(rgb)

cv2.imwrite(sys.argv[2], ws_color)

從上圖中,您可以考慮在每個組件中擬合橢圓以確定矩形。然後您可以使用一些度量來定義組件是否為矩形。這種方法更有可能適用於完全可見的矩形,但對於部分可見的矩形可能會產生不良結果。下圖顯示了這種方法的結果,如果擬合橢圓的矩形在組件面積的 10% 以內,則組件是矩形。

# Fit ellipse to determine the rectangles.
wsbin = numpy.zeros((height, width), dtype=numpy.uint8)
wsbin[cv2.cvtColor(ws_color, cv2.COLOR_BGR2GRAY) != 0] = 255

ws_bincolor = cv2.cvtColor(255 – wsbin, cv2.COLOR_GRAY2BGR)
lbl, ncc = label(wsbin)
for l in xrange(1, ncc + 1):
yx = numpy.dstack(numpy.nonzero(lbl == l)).astype(numpy.int64)
xy = numpy.roll(numpy.swapaxes(yx, 0, 1), 1, 2)
if len(xy) < 100: # Too small. continue ellipse = cv2.fitEllipse(xy) center, axes, angle = ellipse rect_area = axes[0] * axes[1] if 0.9 < rect_area / float(len(xy)) < 1.1: rect = numpy.round(numpy.float64( cv2.cv.BoxPoints(ellipse))).astype(numpy.int64) color = [random.randint(60, 255) for _ in xrange(3)] cv2.drawContours(ws_bincolor, [rect], 0, color, 2) cv2.imwrite(sys.argv[3], ws_bincolor)[/code] 更多的資訊請參考: Advanced square detection (with connected region)

Posted on

高壓縮比編碼格式的介紹 – HEVC

HEVC(H265)介紹

HEVC(High Efficiency Video Coding),也稱為H.265,是一種先進的視頻編解碼標準,是H.264/MPEG-4 AVC的後繼者。相較於H.264,HEVC可以提供更高的視頻質量、更少的碼率和更高的壓縮效率。
HEVC採用更高級的壓縮算法,通過增加更多的預測模式、增加更多的參考幀以及使用更高級別的變換和量化技術,實現了更高的壓縮比。在同樣的視頻質量下,HEVC可以減少50%以上的比特率。
HEVC支持分辨率高達8192×4320的超高清視頻,可以處理各種類型的視頻內容,包括高速運動、低比特率和高動態範圍內容。 HEVC還可以支持多種顏色空間、高級色彩映射和多視點視頻。
由於HEVC壓縮效率高,可以在更低的比特率下提供更高的視頻質量,因此被廣泛用於高清和超高清視頻的傳輸和存儲,例如藍光光盤、在線視頻流媒體、視頻會議等。

瀏覽器對於HEVC的支持


上面若有不支持的瀏覽器,只要支持webassembly,就可以透過webassembly來實現解碼的部分
使用webassembly來達成全平台HEVC的播放器

支持推流端的HEVC透過RTMP推流

OBS在版本29版之後增加的新的編碼支持(H265及AV1)
現在可以透過OBS來推送HEVC格式的流了。

https://blog.csdn.net/karamos/article/details/103508790
FLV規範不支持HEVC(H.265)/AV1,FFmpeg社區對FLV的新視頻編碼算法CodecID也沒有新增定義支持,騰訊視頻雲T-FFmpeg(騰訊視頻雲的FFmpeg維護版本)正在推動社區支持封裝/解封H.265/AV1的FLV的補丁。目前,國內各直播CDN廠商基本支持H.265的封裝和解封,而騰訊視頻雲則針對FLV支持AV1進行了一系列優化。

go2rtc

go2rtc
因為WebRTC是一個非常低延遲的封裝格式,有人寫了這個工具,可以把所有封裝格式的串流轉為WebRTC格式,但是現在WEBRTC對H265的支持仍不高
以下為支持表

Safari 支持 WebRTC,如果您通過以下方式啟用它:

英文版:Develop > Experimental Features > WebRTC H265 codec
中文版:开发 > 实验性功能 > WebRTC H265 codec

Posted on

使用OpenCV判別圖像清晰度

3種清晰度評價方法

  • Tenengrad梯度方法: Tenengrad梯度方法利用Sobel算子分別計算水平和垂直方向的梯度,同一場景下梯度值越高,圖像越清晰。以下是具體實現,這裡衡量的指標是經過Sobel算子處理後的圖像的平均灰度值,值越大,代表圖像越清晰。
  • Laplacian梯度方法: Laplacian()變換不需要區分圖像的x和y方向計算梯度,從上圖的2種kernel也可以看到其x和y方向是對稱的。
  • 方差方法: 方差是概率論中用來考察一組離散數據和其期望(即數據的均值)之間的離散(偏離)成都的度量方法。方差較大,表示這一組數據之間的偏差就較大,組內的數據有的較大,有的較小,分佈不均衡;方差較小,表示這一組數據之間的偏差較小,組內的數據之間分佈平均,大小相近。

圖像清晰度識別之Laplacian算子

Laplacce算子是一種各向同性算子,二階微分算子,在只關心邊緣的位置而不考慮其周圍的像素灰度差值時比較合適。Laplace算子對孤立像素的響應要比對邊緣或線的響應要更強烈,因此只適用於無噪聲圖像。存在噪聲情況下,使用Laplacian算子檢測邊緣之前需要先進行低通濾波。所以,通常的分割算法都是把Laplacian算子和平滑算子結合起來生成一個新的模板。

從模板形式容易看出,如果在圖像中一個較暗的區域中出現了一個亮點,那麼用拉普拉斯運算就會使這個亮點變得更亮。因為圖像中的邊緣就是那些灰度發生跳變的區域,所以拉普拉斯銳化模板在邊緣檢測中很有用。一般增強技術對於陡峭的邊緣和緩慢變化的邊緣很難確定其邊緣線的位置。但此算子卻可用二次微分正峰和負峰之間的過零點來確定,對孤立點或端點更為敏感,因此特別適用於以突出圖像中的孤立點、孤立線或線端點為目的的場合。

在圖像處理中,圖像的清晰程度可以被表示為圖像的邊緣和顏色變化的強度。圖像的清晰度越強,邊緣和顏色變化的強度就越高。因此,通過評估圖像的清晰度,可以檢測圖像是否模糊。

使用範例和結果

import numpy as np
import cv2
from os import listdir
import re
files = [f for f in listdir('./wrong2/') if re.match(r'.*\.jpg', f)]
for i in range(len(files)):
    image = cv2.imread("./wrong2/"+files[i])
    image = cv2.resize(image, (100, 120))
    image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    # Calculate the Laplacian of the image
    laplacian = cv2.Laplacian(image, cv2.CV_64F)
    score = np.var(laplacian)
    print(files[i], score)
cv2.waitKey(0)

下面這張圖片的分數為1099.5216466388888

而這張為2966.9266674375

所以可以知道,由於拉普拉斯是在求邊緣,而模糊偵測就會是一種比較級的狀況,也就是說,如果一個動態的影片,前一偵的邊緣多,後一偵突然變少,有可能就是因為正在移動而造成的模糊導致邊緣變少

Posted on

建立模型與並使用模型來預測資料

從本機圖片建立模型的簡單範例

以下為從classify資料夾內載入圖片並建構分類模組的範例。圖片資料夾的結構如下:

其中daisy、dandelion、roses、sunflowers、tulips為標籤名稱,在各個資料夾內為圖片

然後使用 image_dataset_from_directory 函數載入圖片,需要傳遞以下參數:

  • directory:包含圖片的目錄。
  • labels:標籤的名稱。預設情況下,每個圖片所在的子目錄將被當作標籤。
  • class_names:標籤的名稱列表。
  • label_mode:標籤的格式。可以是 ‘int’(預設值)、’binary’ 或 ‘categorical’。
  • batch_size: 用於指定每個批次中包含的圖片數量。

在載入圖片時,tf.keras.utils.image_dataset_from_directory 函數會使用 RGB 色彩空間。

tf.keras.utils.image_dataset_from_directory 函數中的 seed 參數用於設置隨機數生成器的種子。

如果指定了 seed 參數,則在載入資料集時,將使用指定的種子初始化隨機數生成器,這樣可以保證每次載入資料集的順序都是一致的。

# TensorFlow and tf.keras
import tensorflow as tf
import pathlib
# Helper libraries
import numpy as np
import matplotlib.pyplot as plt
from keras.callbacks import Callback
print(tf.__version__)

batch_size = 32
img_height = 150
img_width = 150

data_dir = pathlib.Path('flower_photos')
# 從資料集中取出訓練資料,需要將 subset 參數設置為 'training'。
train_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size)
# 同時設定了 subset 參數和 validation_split 參數,則會從資料集中取出指定的子集作為驗證集。
val_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size)

class_names = train_ds.class_names
print(class_names)

image_batch, label_batch = next(iter(train_ds))

num_classes = len(class_names)

model = tf.keras.Sequential([
    # 對輸入的張量進行縮放。將輸入的張量除以 255,這將會將輸入的值縮放到 0 到 1 之間。
    tf.keras.layers.Rescaling(1./255),
    tf.keras.layers.Conv2D(32, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(num_classes)
])
model.compile(
    optimizer='adam',
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'])

model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=3
)

model.save('model.h5')

然後按下complier之後,會有下面的樣建模的資訊

因為我們在model.compile的metrics選擇了accuracy,因此評估模型的評估方式會以accuracy作為標準

將資料集分割為訓練集和測試集

上面的程式碼我們呼叫了兩次image_dataset_from_directory來分別載入訓練集和測試集
但也可以只載入一次,然後再將資料集分割為訓練集和測試集

範例程式如下

import tensorflow as tf

# 建立圖片資料集
dataset = tf.keras.utils.image_dataset_from_directory(
    'image_directory',
    labels='inferred',
    class_names=None,
    label_mode='int'
)

# 計算資料集的大小
dataset_size = tf.data.experimental.cardinality(dataset).numpy()

# 計算訓練集和測試集的大小
test_size = int(0.2 * dataset_size)
train_size = dataset_size - test_size

# 將資料集分割為訓練集和測試集
train_dataset = dataset.take(train_size)
test_dataset = dataset.skip(train_size)

用 Keras 載入模型並預測答案

以下為一個簡單範例,使用keras.preprocessing.image.load_img所載入的圖片預設格式為 RGB 圖片

import numpy as np
import tensorflow as tf
from tensorflow import keras
import os
import re
import pathlib
# 載入模型
rank_model = keras.models.load_model('model.h5')
# 讓模型輸出的機率值分布在0-1之間
probability_rank_model = tf.keras.Sequential([rank_model, tf.keras.layers.Softmax()])

class_names = ['daisy','dandelion','roses','sunflowers','tulips']

img = keras.preprocessing.image.load_img('./test.jpg', target_size=(150, 150))
if img is None:
    continue
x = keras.preprocessing.image.img_to_array(img)
x = np.expand_dims(x, axis=0)
pred = probability_rank_model.predict(x)[0]
print(path)
top_inds = pred.argsort()[::-1][:5]
if pred[top_inds[0]] > 0.8:
    print('result is: ' + class_names[top_inds[0]])
# 印出機率分布
for data in top_inds:
    print('    {:.3f}  {}'.format(pred[data], class_names[data]))

上面是使用keras.preprocessing.image.load_img來載入圖片
我們也可以使用OpenCV來載入圖片使用

# 使用 OpenCV 讀取圖片
image = cv2.imread('image.jpg')

# 將圖片轉換為 RGB 格式
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = PIL.Image.fromarray(image)
x = keras.preprocessing.image.img_to_array(image)
probability_rank_model.predict(x.tolist())

如何關掉預測時吐出的log

在使用 predict 方法進行預測時,Keras 模型可能會輸出一些訓練過程中的訊息,如果你想要避免輸出這些訊息,則可以在建立模型時設置 verbose 參數。

例如,如果你想要在預測時停止輸出訓練過程中的訊息,則可以在建立模型時使用以下代碼:

# 預測
predictions = model.predict(x_test, verbose=0)

這樣,在進行預測時就不會再輸出任何訓練過程中的訊息了。

Posted on

TensorFlow Extended (TFX) 介紹

功能介紹

TensorFlow Extended (TFX) 是Google 開發的一個開源框架,用於在TensorFlow 中構建機器學習管道。TFX 的目標是簡化機器學習管道的構建過程,使其能夠更容易地部署和維護。

其中TFX 管道是TFX 中的一個重要部分,它是一種用於組織和管理機器學習工作流的方式。TFX 管道由多個組件組成,每個組件負責執行特定的任務,如數據預處理、訓練、評估等。TFX 管道中的組件可以由TFX 提供的組件使用,也可以使用自定義組件。

TFX 管道通過使用Apache Airflow 或Kubeflow Pipelines 在組件之間傳遞數據來實現自動化的機器學習工作流。這樣可以在一個可視化的界面上監控和管理管道執行過程,並且在管道中的每個步驟之間自動傳遞數據。

強大的資料處理工具

TFDV 結合開源的 Facets ,是可以幫助理解和分析機器學習數據集的開源可視化工具,在 Google AI Blog 中展示了透過 Facets 視覺化抓出 CIFAR-10 資料集中一個錯誤分為貓咪的青蛙的圖片。

TFDV 容許兩個資料及之間的分布對照,例如訓練資料與測試資料,迅速抓出資料飄移與偏斜情形,而 TFDV 更進一步可以做到修正與納入新特徵,以及整合在筆記本及 TFX 之中。

PS: 以上資料來自於Day 14 : 資料驗證 TensorFlow Data Validation (TFDV)

安裝TFX

pip install tfx
pip install -i https://pypi-nightly.tensorflow.org/simple --pre tfx

上文命令會安裝 TFX 的主要依賴項的軟件包,例如 TensorFlow 模型分析 (TFMA)、TensorFlow 數據驗證 (TFDV)、TensorFlow 轉換 (TFT)、TFX 基本共享庫 (TFX-BSL)、ML 元數據 ( MLMD)。

這些組件之間的數據流


下圖說明了TFX庫與流水線組之間的關係:

研究感想

這個工具組裡面的TFDV非常的吸引我,但是後來發現若要使用TFDV去驗證資料,前面還是需要把資料集先經過前面FTX的流水線處理過後,才有辦法使用Facets去可視化已經經過驗證後的資料結果。Facets並沒有辦法直接讀入未經TFX流水線處理過的原始資料集,而FTX對現在的我還有一點複雜,所以先大概知道有這個工具,然後以後再來慢慢摸索。

Posted on

用兩張圖片來偵測圖像是否在靜止狀態

實踐概念

使用 TensorFlow 的圖像處理函數,將兩張圖片讀入並進行比對。

比如您可以使用 OpenCV 庫將圖片讀入,然後使用 TensorFlow 庫對兩張圖片進行比對。 您可以使用 TensorFlow 的圖像處理函數,例如圖像縮放、旋轉和鏡像轉換等,對兩張圖片進行預處理。接著您可以使用 TensorFlow 中的數值計算函數,例如 mean square error (MSE) 或 structural similarity index (SSIM)等,對兩張圖片進行比對。

如果比對結果的值非常小,則可以認為兩張圖片非常相似,反之則表示兩張圖片不相似。

Structural Similarity Index (SSIM)

Structural Similarity Index (SSIM) 是一種用於評估圖像相似度的指標。它是基於人眼視覺系統的特性,模擬人眼對圖像質量的敏感度,並使用三個要素來評估圖像相似度:亮度、對比度和結構。

亮度(luma): 表示圖像中所有像素的平均亮度
對比度(contrast): 表示圖像中各像素亮度值的差異程度
結構(structure): 表示圖像中各像素之間的相關性
SSIM 使用這三個要素的加權和來計算圖像之間的相似度,其值介於 -1 到 1 之間,值越大則表示圖像相似度越高。

SSIM 有很好的相似度表示能力,在圖像壓縮、還原和質量評估等領域有著廣泛的應用。

使用Tensorflow實現SSIM的簡單範例

以下是使用 TensorFlow 實現 SSIM 的簡單範例,該範例使用兩張圖片來比較它們的相似度:

import tensorflow as tf

# 讀入兩張圖片
img1 = tf.io.read_file('image1.jpg')
img2 = tf.io.read_file('image2.jpg')
img1 = tf.image.decode_jpeg(img1, channels=3)
img2 = tf.image.decode_jpeg(img2, channels=3)

# 計算兩張圖片的 SSIM
ssim = tf.image.ssim(img1, img2, max_val=255)

# 顯示 SSIM 值
print(ssim)

這個範例中,我們使用 TensorFlow 的 I/O 函數來讀入兩張圖片,使用 decode_jpeg 函數將圖片解碼為 Tensor,然後使用 ssim 函數對兩張圖片進行比對,最終顯示 SSIM 值。

注意: 這個範例中,由於是在計算兩張圖片的 SSIM,所以需要兩張圖片大小相同,如果不同需要先對兩張圖片進行resize或pad。

Posted on

Tensorflow裡Estimator介紹

tf.estimator.Estimator 介紹

官方介紹頁面: https://www.tensorflow.org/guide/estimator
tf.estimator.Estimator tf.keras.Model 類似,estimator是模型級別的抽象。tf.estimator提供了一些目前仍在為 tf.keras 開發中的功能。包括:

  • 基於參數服務器的訓練
  • 完整的TFX集成

使用預製Estimator,能夠在比基礎TensorFlow API 高很多的概念層面上工作。您無需再擔心創建計算圖或會話,因為Estimator 會替您完成所有“基礎工作”。此外,使用預製Estimator,您只需改動較少代碼就能試驗不同的模型架構。例如,tf.estimator.DNNClassifier是一個預製Estimator 類,可基於密集的前饋神經網絡對分類模型進行訓練。

它提供了一種統一的方式來實現和使用各種不同類型的機器學習模型。它提供了一些預定義的模型類型,如線性迴歸、決策樹、KNN等,可以讓開發者更輕鬆的實現常見的機器學習模型。

主要功能

tf.estimator 是 TensorFlow 的一個高級 API,它提供了一種統一的方式來實現和使用各種不同類型的機器學習模型。它的主要用途是簡化建立、訓練和使用模型的流程,使開發人員能夠更輕鬆地實現常見的機器學習模型。

其主要功能如下:

  • 提供預定義的模型類型, 如線性迴歸,決策樹和KNN等
  • 提供一組工具,可以幫助您訓練,評估和預測模型
  • 提供一個統一的界面,使得用戶能夠更容易地訓練、評估和使用模型
  • 支持模型訓練和預測的分布式計算
  • 具有模型檢查点和恢復功能,可以將模型訓練過程中的狀態保存到磁盤上

優點

  • 提供了一個統一的界面,使開發人員能夠更輕鬆地實現常見的機器學習模型
  • 提供了一組工具,可以幫助您訓練,評估和預測模型
  • 支持模型訓練和預測的分布式計算
  • 具有模型檢查点和恢復功能,可以將模

在2.0裡被標註為棄用

在官網的API文件裡可以發現這個類別旁邊被註記了【棄用標籤】

在 TensorFlow 2.0 中,tf.estimator 被宣布即將棄用是因為它與 TensorFlow 的其他部分存在一些不一致性。隨著 TensorFlow 進化,其他部分,特別是 Keras API,已經成為了更簡單,更直接的機器學習模型構建選項。

Keras API 提供了一個統一的界面,可以用於創建和調用各種不同類型的模型,並且與 TensorFlow 的其他部分(如 TensorFlow 的低級計算圖 API)更加一致。

另外,Keras API 更加易於學習和使用,並且支持更多的高級功能,如自動微調。因此,TensorFlow 的開發團隊決定將重點轉移到 Keras API 上,並將 tf.estimator 標記為即將棄用。

學習 Keras API 更簡單,並且與其他 TensorFlow 特性更加一致,因此官方才會建議將重點轉向學習 Keras API。

使用Keras API來取代tf.estimator.Estimator的範例

tf.estimator.LinearRegressor 的使用範例

import tensorflow as tf

# Define the input function
def input_fn(features, labels, batch_size):
    dataset = tf.data.Dataset.from_tensor_slices((features, labels))
    dataset = dataset.shuffle(1000).batch(batch_size)
    return dataset

# Define the feature columns
feature_columns = [tf.feature_column.numeric_column("x", shape=[1])]

# Define the LinearRegressor
regressor = tf.estimator.LinearRegressor(feature_columns=feature_columns)

# Define the training inputs
train_input_fn = lambda: input_fn(x_train, y_train, batch_size=batch_size)

# Train the model
regressor.train(input_fn=train_input_fn, steps=1000)

# Define the testing inputs
test_input_fn = lambda: input_fn(x_test, y_test, batch_size=batch_size)

# Evaluate the model
metrics = regressor.evaluate(input_fn=test_input_fn)
print("Mean squared error: ", metrics["average_loss"])

把上面的程式碼使用Keras API改寫

from tensorflow import keras

# Define the model
model = keras.Sequential()
model.add(keras.layers.Dense(1, input_shape=[1]))

# Compile the model
model.compile(optimizer='sgd', loss='mean_squared_error')

# Train the model
model.fit(x_train, y_train, epochs=100)

在上面的程式碼之中,線性回歸的設定是來自於以下compile所設定的參數
model.compile(optimizer='sgd', loss='mean_squared_error')
使用 ‘sgd’ 作為優化器和 ‘mean_squared_error’ 作為損失函數是常用於實現線性迴歸模型的設定。

‘SGD’ 是隨機梯度下降的縮寫,它是一種優化算法,用於尋找最小化損失函數的權重值。’mean_squared_error’ 是一種常用的損失函數,用於計算預測值和實際值之間的差距。

在線性迴歸中, 我們嘗試找到一組權重和偏差, 使得預測值和實際值之間的差距最小, 而 mean_squared_error 是計算預測值和實際值之間差距的常用方法, 所以使用 mean_squared_error 作為損失函數是適當的.

tf.estimator.train_and_evaluate

tf.estimator.train_and_evaluate是TensorFlow Estimator API 中的一個函數,用於在訓練和評估模型時自動管理訓練循環。

它接受兩個參數:一個是估計器對象,另一個是一個字典,其中包含訓練和評估所需的所有配置信息。

在訓練期間,該函數會自動調用估計器的train() 方法,並在訓練過程中記錄訓練步驟和損失值。在評估期間,該函數會自動調用估計器的evaluate() 方法,並在評估過程中記錄評估指標。

通過使用tf.estimator.train_and_evaluate可以簡化訓練和評估模型的流程,避免手動管理訓練循環和評估過程中的各種細節。這樣可以讓開發人員專注於模型的構建和調優,而不必擔心訓練和評估的細節實現。

使用示例如下:

# Define the estimator
estimator = tf.estimator.LinearClassifier(feature_columns=feature_columns)

# Define the training and evaluation specs
train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn, max_steps=1000)
eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn)

# Train and evaluate the estimator
tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec)

可以使用Keras API 來使用tf.estimator.train_and_evaluate。
使用TensorFlow 的Keras API 和Estimator API 來構建模型時,可以使用 tf.keras.estimator.model_to_estimator() 函數將Keras 模型轉換為Estimator,然後使用 tf.estimator.train_and_evaluate() 函數訓練和評估模型。

示例如下:

import tensorflow as tf
from tensorflow import keras

# Define the Keras model
def build_model():
    model = keras.Sequential([
        keras.layers.Dense(64, activation='relu', input_shape=(784,)),
        keras.layers.Dense(64, activation='relu'),
        keras.layers.Dense(10, activation='softmax')
    ])
    model.compile(optimizer=tf.keras.optimizers.Adam(),
                  loss=tf.keras.losses.categorical_crossentropy,
                  metrics=[tf.keras.metrics.categorical_accuracy])
    return model

# Convert the Keras model to an Estimator
estimator = tf.keras.estimator.model_to_estimator(keras_model=build_model())

# Define the input function for training
def train_input_fn():
    pass  # return training data

# Define the input function for evaluation
def eval_input_fn():
    pass  # return evaluation data

# Train and evaluate the Estimator
tf.estimator.train_and_evaluate(estimator,
                                train_spec=tf.estimator.TrainSpec(train_input_fn),
                                eval_spec=tf.estimator.EvalSpec(eval_input_fn))