Posted on

使用Tensorboard來觀察模型效能

Tensorboard介紹

在機器學習中,要改進模型的某些參數,通常需要觀察模型數據以進行衡量。TensorBoard 是用於提供機器學習工作流期間所需測量和呈現的工具。它使您能夠跟踪實驗指標(例如損失和準確率),呈現模型計算圖,將嵌入向量投影到較低維度的空間等。

TensorBoard是TensorFlow的可視化工具,用於可視化和分析機器學習實驗的結果。它提供了一組豐富的可視化面板,可以幫助您理解、調節測試和優化訓練過程中的模型。

主要功能和用途

以下是TensorBoard的一些主要功能和用途:

  1. Scalar Dashboard(標量儀表板):顯示訓練過程中的標量指標,如損失函數、準確率、學習率等。您可以隨時跟踪這些指標間的變化,並比較不同實驗之間的結果。
  2. Graph Dashboard(圖形儀表板):顯示模型的計算圖,可視化網絡結構和層之間的連接。這有助於理解和調試模型的結構,查看各層的輸入輸出形狀。
  3. 直方圖儀表板(直方圖儀表板):顯示權重、加權和激活函數的分配情況。這用於觀察參數隨時間的變化、檢測梯度消失或突然爆炸等問題很有幫助。
  4. Image Dashboard(圖像儀表板):可視化輸入圖像、模型生成的圖像或模型層輸出的特徵圖等。這對於在圖像數據中觀察模型上的表現和理解卷積網絡中的特徵提取過程非常有用。
  5. Embedding Dashboard(嵌入儀表板):用於可視化高維嵌入空間中的數據。您可以在三維空間中探索和比較嵌入向量,以及在嵌入空間中相似的樣本。
  6. Projector Dashboard(投影儀表板):提供了一種交互式的界面,可以在高維空間中對數據進行探索和分析。您可以通過選擇和過濾樣本、聚類分析和降維等操作來理解數據集的結構。

TensorBoard 提供了一種觀察的方式來監視和分析機器學習模型,以了解模型的行為並進行調優。您可以使用 TensorFlow 的相關API(如)將數據寫入TensorBoard日誌文件,然後在命令tf.summary中行中運行 TensorBoard 來查看和分析結果。

如何使用

以下為一個範例程式

import tensorflow as tf
import datetime

mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

def create_model():
  return tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model = create_model()
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
# 設定log的資料夾
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
# 設定tensorboard的callback
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

model.fit(x=x_train, 
          y=y_train, 
          epochs=5, 
          validation_data=(x_test, y_test), 
          callbacks=[tensorboard_callback])

接著就可以在所指定的資料夾位置運行tensorboard介面

tensorboard --logdir logs/fit

接著開啟http://localhost:6006就可以看到如下畫面,這個畫面是Scalars,顯示損失和指標在每個時期如何變化

Graphs用以可視化模型。可以看出你的模型是如何建構的,有用到那些方法

Distributions是顯示張量隨時間的分佈。DistributionsHistograms這兩頁很相似,可了解模型權重隨時間的變化,作為判斷是否出現問題的初步衡量標準。

Histograms是使用Ridgeline plot來顯示張量隨時間的分佈,橫軸為epochs訓練的次數,縱軸則為權重和偏差的值

Time-Seriesscalars非常相似,都是在評估準確度。然而,一個區別是每次訓練迭代而不是每個時期對目標指標的觀察。

Posted on

如何訓練和測試數據

將資料切分

在數據科學中,訓練數據和測試數據扮演著兩個主要的角色。評估已構建模型的性能與訓練和構建模型同樣重要,因為未經評估性能的模型可能會產生錯誤的預測並導致嚴重的並發症。為了防止這種情況發生並確保預測的準確性,您必須足夠好地測試和驗證模型。

為了構建和評估機器學習模型的性能,我們通常將數據集分成兩個不同的數據集。這兩個數據集是訓練數據測試數據

訓練數據測試數據
用於構建模型用於評估構建的模型
分配更大的數據部分分配較小的數據部分
可進一步劃分以進行驗證不會進一步分割

什麼是驗證數據

驗證數據是從訓練數據中分離出來的子數據集,用於在訓練過程中驗證模型。來自驗證過程的信息幫助我們改變模型的參數、分類器以獲得更好的結果。所以基本上,驗證數據可以幫助我們優化模型。 

使用 Scikit-learn 的 train_test_split來切割數據

使用下面這段程式碼可以將訓練及分割成訓練及驗證集

from sklearn.model_selection import train_test_split
# 載入資料集
....
# 分離訓練和測試數據
X_train, X_val, y_train, y_val = train_test_split(train_images_fold, train_labels_fold, test_size=0.1, random_state=42)
model = create_model()
keras_classifier.fit(X_train, y_train, validation_data=(X_val, y_val))

使用測試資料作驗證

下面這段程式可以使用X_test、y_test來使用model做測試,並且可以用accuracy_score來取得準確率,並將準確率存入一個陣列裡

predictions = keras_classifier.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
accuracy_scores.append(accuracy)

若是想要取得錯誤的資料集,則可以用np.where來取得與正確答案不一樣的資料,並將錯誤的資料存入incorrect陣列裡面

incorrect_indices = np.where(predictions != y_test)[0]
incorrect_images = X_test[incorrect_indices]
incorrect_labels = y_test[incorrect_indices]
incorrect_prediction = predictions[incorrect_indices]
for i in range(len(incorrect_indices)):
    incorrect.append({"image": incorrect_images[i] ,"label": incorrect_labels[i], "pred": incorrect_prediction[i], "idx": fold_index})

完整範例

下面的範例為結合K-Fold概念,將資料及分成五份,並做五次的訓練以判別模型的訓練狀況是否有過擬合的狀況。其中會每次會取其4分來做訓練、1份做測試,再將訓練集中的1/10拆做驗證集。最後使用matplotlib.pyplot來顯示這五次之中,測試集中錯誤結果的圖片

import numpy as np
import matplotlib.pyplot as plt
import pathlib
from sklearn.model_selection import StratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
from tensorflow import keras
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_validate

img_path = 'dice3'

def class_names(path):
    return np.array(sorted([item.name for item in pathlib.Path(path).glob('*') if
                            "LICENSE.txt" != item.name]))
def create_model():
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Rescaling(1. / 255))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=7, activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D())
    model.add(tf.keras.layers.Conv2D(64, kernel_size=5, activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D())
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(64, activation='relu'))
    model.add(tf.keras.layers.Dense(len(class_names(img_path))))

    model.compile(
        optimizer='adam',
        loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
        metrics=['accuracy'])
    return model


train_ds = tf.keras.utils.image_dataset_from_directory(
  img_path,
  seed=7,
  batch_size=32)
train_images = []
train_labels = []

for images, labels in train_ds:
    train_images.append(images.numpy())
    train_labels.append(labels.numpy())

train_images = np.concatenate(train_images, axis=0)
train_labels = np.concatenate(train_labels, axis=0)
label_encoder = LabelEncoder()
train_labels_encoded = label_encoder.fit_transform(train_labels)

# 創建KerasClassifier
keras_classifier = KerasClassifier(build_fn=create_model, epochs=5, batch_size=16)

# 定義StratifiedKFold
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=123)

fold_index = 1
# 進行交叉驗證
accuracy_scores = []
incorrect = []
for train_indices, val_indices in kfold.split(train_images, train_labels_encoded):
    print("fold_index="+str(fold_index))
    train_images_fold = train_images[train_indices]
    train_labels_fold = train_labels_encoded[train_indices]

    X_test = train_images[val_indices]
    y_test = train_labels_encoded[val_indices]
    
    # 分離訓練和測試數據
    X_train, X_val, y_train, y_val = train_test_split(train_images_fold, train_labels_fold, test_size=0.1, random_state=42)
    model = create_model()
    keras_classifier.fit(X_train, y_train, validation_data=(X_val, y_val))

    predictions = keras_classifier.predict(X_test)
    accuracy = accuracy_score(y_test, predictions)
    accuracy_scores.append(accuracy)

    incorrect_indices = np.where(predictions != y_test)[0]
    incorrect_images = X_test[incorrect_indices]
    incorrect_labels = y_test[incorrect_indices]
    incorrect_prediction = predictions[incorrect_indices]
    for i in range(len(incorrect_indices)):
        incorrect.append({"image": incorrect_images[i] ,"label": incorrect_labels[i], "pred": incorrect_prediction[i], "idx": fold_index})
    # 印出準確率
    print("Accuracy scores:", accuracy_scores)
    print("Mean accuracy:", np.mean(accuracy_scores))
    fold_index += 1

# 顯示出錯誤的答案
images_per_page = 15
num_images_per_row = 5
num_images_per_col = 3
        
num_pages = (len(incorrect) - 1) // images_per_page + 1
for page in range(num_pages):
    start_idx = page * images_per_page
    end_idx = (page + 1) * images_per_page
    page_detail = incorrect[start_idx:end_idx]
    
    fig, axes = plt.subplots(num_images_per_col, num_images_per_row, figsize=(num_images_per_col*2, num_images_per_row*2))
    i=0
    for data in page_detail:
        image = data["image"]
        label = data["label"]
        pred = data["pred"]
        idx = data["idx"]
        row = i // num_images_per_row
        col = i % num_images_per_row
        ax = axes[row, col]
        image = image.astype(np.uint8)
        ax.imshow(image)
        ax.set_title(f"{label}->{pred}({idx})")
        i = i+1
        ax.axis("off")

    plt.tight_layout()
    #plt.suptitle("fold_index:"+str(fold_index)+"-"+str(page+1), x=0, y=1, ha='left', va='top')
    plt.show()

參考資料

Posted on

k-Fold Cross-Validation(交叉驗證)

一般所使用的方式 – Holdout method

這是一種非常基本且簡單的方法,我們將整個數據集分為兩部分,即訓練數據測試數據。顧名思義,我們在訓練數據上訓練模型,然後在測試集上進行評估。通常,訓練數據的大小設置為測試數據的兩倍以上,因此數據按70:30或80:20的比例進行分割。

在這種方法中,數據在分割之前首先被隨機洗牌。由於模型是在不同的數據點組合上訓練的,每次訓練時模型都會給出不同的結果,這可能是不穩定的原因。此外,我們永遠無法保證我們選擇的訓練集能夠代表整個數據集。

此外,當我們的數據集不是太大時,測試數據很可能包含一些我們丟失的重要信息,因為我們沒有在測試集上訓練模型。範例程式如下:

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)

改善方法 – k-Fold Cross-Validation

K 折交叉驗證是改進的一種方法。這種方法保證了我們模型的分數不依賴於我們選擇訓練集和測試集的方式。將數據集分為 k 個子集,並將保留方法重複 k 次。讓我們分步驟完成這個過程:

  1. 將整個數據集隨機分成 k 個折疊(子集)
  2. 對於數據集中的每個折疊,在數據集的 k – 1 個折疊上構建模型。然後,測試模型以檢查第 k 次折疊的有效性
  3. 重複此操作,直到每個 k 重都作為測試集
  4. k 記錄的準確度的平均值稱為交叉驗證準確度,並將作為模型的性能指標。

因為它確保來自原始數據集的每個觀察結果都有機會出現在訓練和測試集中,所以與其他方法相比,這種方法通常會產生偏差較小的模型。如果我們的輸入數據有限,這是最好的方法之一。 

這種方法的缺點是訓練算法必須從頭開始重新運行 k 次,這意味著進行評估需要 k 倍的計算量。

分層k-Fold – Stratified K Fold Cross Validation

在分類問題上使用 K Fold 可能會很棘手。由於我們隨機打亂數據,然後將其劃分為折疊,因此我們可能會得到高度不平衡的折疊,這可能會導致我們的訓練出現偏差。例如,讓我們以某種方式得到一個折疊,其中大多數屬於一個類(例如正類),而只有少數屬於負類。這肯定會破壞我們的訓練,為了避免這種情況,我們使用分層進行分層折疊。

分層是重新排列數據的過程,以確保每次折疊都能很好地代表整體。例如,在每個類包含 50% 數據的二元分類問題中,最好對數據進行排列,使得在每個折疊中,每個類包含大約一半的實例。

下面這張圖,是在分類問題上使用K-Fold折疊的方式,會發現因為不同分類隨機分組導致每個分組的數量不一,而破壞訓練的狀況

資料狀況: 共三種資料,每一種70張,共210張,訓練集168張,測試集42張

而改使用StratifiedKFold去做資料分組之後,能看到訓練過程更加順利

範例程式

以下為使用Tensorflow加上sklearn.model_selection去做資料分組的測試程式

import numpy as np
import pathlib
import tensorflow as tf
from sklearn.model_selection import LeaveOneOut,KFold
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
from sklearn.datasets import load_iris

img_path = 'dice3'
train_ds = tf.keras.utils.image_dataset_from_directory(
  img_path,
  seed=7,
  batch_size=32)
X = []
y = []

for images, labels in train_ds:
    X.append(images.numpy())
    y.append(labels.numpy())

X = np.concatenate(X, axis=0)
y = np.concatenate(y, axis=0)
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(y)

def class_names(path):
    return np.array(sorted([item.name for item in pathlib.Path(path).glob('*') if
                            "LICENSE.txt" != item.name]))
def create_model():
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Rescaling(1. / 255))
    model.add(tf.keras.layers.Conv2D(32, kernel_size=7, activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D())
    model.add(tf.keras.layers.Conv2D(64, kernel_size=5, activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D())
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(64, activation='relu'))
    model.add(tf.keras.layers.Dense(len(class_names(img_path))))
    model.compile(
        optimizer='adam',
        loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
        metrics=['accuracy'])
    return model

def make_dataset(X_data,y_data,n_splits):
    def gen():
        for train_index, test_index in KFold(n_splits).split(X_data):
            X_train, X_test = X_data[train_index], X_data[test_index]
            y_train, y_test = y_data[train_index], y_data[test_index]
            yield X_train,y_train,X_test,y_test

    return tf.data.Dataset.from_generator(gen, (tf.float64,tf.float64,tf.float64,tf.float64))

dataset=make_dataset(X,y,5)


for X_train,y_train,X_test,y_test in dataset:
    print(len(X_train), len(y_train), len(X_test), len(y_test))
    model = create_model()
    model.fit(X_train, y_train, epochs=10, batch_size=32, validation_data=(X_test, y_test))


若要改成使用分層K-Fold,則要使用sklearn.model_selection的StratifiedKFold

def make_dataset(X_data,y_data,n_splits):
    def gen():
        kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=123)
        for train_index, test_index in kfold.split(X_data, y_data):
            X_train, X_test = X_data[train_index], X_data[test_index]
            y_train, y_test = y_data[train_index], y_data[test_index]
            yield X_train,y_train,X_test,y_test

    return tf.data.Dataset.from_generator(gen, (tf.float64,tf.float64,tf.float64,tf.float64))

資料集長這樣

參考資料

Posted on

Optimization loop failed: Cancelled: Operation was cancelled

遇到的錯誤訊息

2023-06-14 16:32:39.652288: W tensorflow/core/data/root_dataset.cc:167] Optimization loop failed: Cancelled: Operation was cancelled

這個錯誤訊息代表TensorFlow 在優化過程中遇到了問題,並且操作被取消了。當操作被取消時,TensorFlow 無法完成所需的計算任務,因此無法產生期望的結果。具體來說,如果你在使用 TensorFlow 的 CPU 模式下運行一個優化循環,並且該循環被取消,以下情況可能發生:

  • 訓練過程中斷:如果你正在訓練一個模型,操作取消將導致訓練過程中止。你將無法完成整個訓練過程,無法獲得最終訓練好的模型。
  • 中斷的結果:在某些情況下,如果操作被取消,TensorFlow 可能會嘗試返回已經計算出的部分結果。這取決於具體的操作和中斷發生的時間點。然而,這些部分結果通常不是完整或不可用的。
  • 推斷錯誤:如果你正在使用模型進行推斷(如輸入一條文本獲取其分類),而操作被取消,你將無法獲得模型對輸入的預測結果。這可能導致你無法得知模型的輸出,或者輸出結果不完整、不准確。

為什麼會出現此錯誤

大部分的狀況是因為資源不足,導致運算被中斷

  • 計算資源不足:TensorFlow 在 CPU 模式下運行時,對於大型、複雜的模型或數據集,可能需要較長的時間來完成計算。如果計算資源有限,操作可能會被取消。
  • 內存不足:TensorFlow 在處理大型模型或數據時需要大量內存。如果內存不足,操作可能會被取消。
  • 配置錯誤:有時,TensorFlow 的配置可能不正確,導致操作無法成功完成。這可能包括錯誤的版本或依賴問題。

觀察電腦CPU與MEMORY使用狀況

要解決此問題,首先要先觀察是哪一部分出了問題,就需要在程式內去監控資源的使用。請參考此篇教學: How to get current CPU and RAM usage in Python

    # Importing the library
    import psutil


    # Getting % usage of virtual_memory ( 3rd field)
    if psutil.virtual_memory()[2] > 80:
        print(time.strftime("%Y-%m-%d_%H-%M-%S") + ' RAM memory % used:', psutil.virtual_memory()[2])
        # Getting usage of virtual_memory in GB ( 4th field)
        print('RAM Used (GB):', psutil.virtual_memory()[3]/1000000000)
    
    cpu_usage = psutil.cpu_percent()
    if cpu_usage > 80:
        print(time.strftime("%Y-%m-%d_%H-%M-%S") + ' The CPU usage is: ', cpu_usage)

解決 – 若是使用太多記憶體

在Tensorflow裡面有一篇討論串,滿多人都會遇到此問題,其中一位大大建議在GPU上提高可使用的內存,可解決此問題

gpus = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(gpu, True)

討論串: Optimization loop failed: Cancelled: Operation was cancelled

另外也可以考慮設置合適的 batch_size、限制模型大小或減少數據集規模是一種在沒有 GPU 或內存受限的情況下控制內存使用的方法。下面是一些方法可以考慮:

  • 調整 batch_size:batch_size 是每次模型訓練時用於處理的樣本數量。較大的 batch_size 可能會導致內存不足。你可以嘗試降低 batch_size 的值,以減少內存需求。但要注意,較小的 batch_size 可能會影響訓練過程的穩定性和收斂速度。
  • 限制模型的大小:模型的大小直接影響內存使用。你可以嘗試以下方法來限制模型的大小:
    • 減少模型的層數或參數數量。
    • 使用輕量級模型架構,例如 MobileNet、SqueezeNet 等。
    • 使用模型剪枝技術,減少模型中冗餘參數的數量。
    • 使用低精度(如 16 位浮點數)表示模型的參數,以減少內存佔用。
  • 減少數據集規模:如果數據集過大而導致內存不足,你可以考慮以下方法來減少數據集的規模:
    • 隨機採樣:從原始數據集中隨機選擇一部分樣本作為子集進行訓練。確保採樣後的數據集仍具有代表性。
    • 數據集切片:將大型數據集切割成多個較小的部分,逐個部分進行訓練。可以使用交叉驗證等技術來利用所有數據並評估模型性能。

解決 – 若是使用太多CPU

可嘗試以下方法來減少 CPU 的消耗:

  • 減少數據的處理量:優化輸入數據的大小和復雜度,可以減少 CPU 的負載。例如,可以嘗試縮小圖像尺寸、減少輸入序列的長度或降低數據的維度。
  • 使用批處理預測:通過將多個樣本組成一個批次進行預測,可以減少每個樣本的計算和內存佔用。 Model.predict() 方法默認可以處理批量輸入,你可以將多個樣本一起傳遞給該方法進行預測。
  • 並行處理:如果你的系統支持多線程或多進程,並且你的模型和數據可以進行並行處理,可以使用並行化方法來提高 CPU 的利用率。例如,使用 Python 的 multiprocessing 模塊或 TensorFlow 的 tf.data.Dataset 的並行化功能。
  • 優化模型:檢查你的模型結構和計算圖,看是否有可以優化的部分。可能存在一些冗餘計算或可以簡化的操作。你可以嘗試使用 TensorFlow 的圖優化工具,如 tf.function 和 tf.autograph,來加速模型的計算過程。
  • 使用更高效的庫或算法:某些情況下,使用其他庫或算法可能比 TensorFlow 更高效。你可以嘗試使用其他機器學習庫,如 PyTorch 或 Scikit-learn,並根據你的需求選擇更適合的庫和算法。
  • 升級硬件:如果你的計算機配置允許,可以考慮升級到更強大的 CPU 或添加更多的 CPU 核心。這樣可以提高系統的並行處理能力和整體性能。
Posted on

計算兩個點之間的直線距離

使用math.hypot

math.hypot 是 Python 內置的數學模塊 math 中的函數。它接受兩個參數,分別代表兩點的 x 和 y 坐標差值,然後返回它們的歐幾里德距離(即直線距離)。

import math

x1, y1 = 1, 2
x2, y2 = 3, 4

distance = math.hypot(x2 - x1, y2 - y1)
print(distance)

使用np.sqrt

np.sqrt 是 NumPy 庫中的函數,用於計算給定數值的平方根。要使用 np.sqrt 計算兩點之間的距離,你需要首先計算兩點在 x 和 y 坐標軸上的差值的平方和,然後將它們相加,再使用 np.sqrt 對結果進行平方根運算。

import numpy as np

x1, y1 = 1, 2
x2, y2 = 3, 4

distance = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
print(distance)

Posted on

對輪廓的點做旋轉計算

使用角度的正弦和餘弦函數,將長方形的寬度和高度乘以正確的係數,以獲得旋轉後的角點座標。

下面為一個將一個長方形的四個點作45度旋轉的簡單範例:

import cv2
import numpy as np
import math

base_x = 100
base_y = 100
width = 100
height = 50
angle = 45

# 計算長方形中心點座標
center_x = base_x + (width // 2)
center_y = base_y + (height // 2)

# 將角度轉換為弧度
angle_rad = math.radians(angle)

# 計算長方形四個角的相對座標
cos_val = math.cos(angle_rad)
sin_val = math.sin(angle_rad)
x = width / 2
y = height / 2

# 計算四個角點座標
point1 = (int(center_x - x * cos_val + y * sin_val), int(center_y - x * sin_val - y * cos_val))
point2 = (int(center_x + x * cos_val + y * sin_val), int(center_y + x * sin_val - y * cos_val))
point3 = (int(center_x + x * cos_val - y * sin_val), int(center_y + x * sin_val + y * cos_val))
point4 = (int(center_x - x * cos_val - y * sin_val), int(center_y - x * sin_val + y * cos_val))

print("Point 1:", point1)
print("Point 2:", point2)
print("Point 3:", point3)
print("Point 4:", point4)

# 創建空白影像
image = np.zeros((500, 500, 3), dtype=np.uint8)

# 轉換座標為Numpy陣列
pts = np.array([point1, point2, point3, point4], dtype=np.int32)

# 繪製多邊形
cv2.polylines(image, [pts], True, (0, 255, 0), thickness=2)

# 顯示結果
cv2.imshow('Rectangle', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

顯示結果如下

Posted on

點到多邊形的最短距離

cv2.pointPolygonTest是OpenCV中的一個函數,用於計算點到多邊形的最短距離或點是否在多邊形內。

函數的語法如下:

_, intersection = cv2.pointPolygonTest(rect3, tuple(line1_midpoint), measureDist=False)
  • contour:多邊形的輪廓,可以是Numpy陣列或OpenCV的輪廓物件。
  • point:要計算距離的點,通常是一個(x, y)座標元組。
  • measureDist:指定是否計算點到多邊形的最短距離。如果為True,則返回距離值;如果為False,則返回一個整數值表示點的位置關係:正數表示點在多邊形內部、負數表示點在多邊形外部、0表示點在多邊形邊界上。

相關函數請參考: cv2.distanceTransform

另外要畫出多邊形可使用cv2.polylines,如以下範例

import cv2
import numpy as np

# 定義長方形的四個角點
rect1 = np.array([[100, 100], [300, 100], [300, 200], [100, 200]])

# 創建空白影像
image = np.zeros((500, 500, 3), dtype=np.uint8)

# 繪製長方形
cv2.polylines(image, [rect1], True, (0, 255, 0), thickness=2)

# 顯示結果
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Posted on

使用OpenCV將圖形轉正

旋轉圖片的方法

若是單純只是要把圖片做角度的旋轉,可以直接使用OpenCV 的 cv2.rotate() 函数。可按指定的方向旋轉圖像。如下:

import cv2

# 讀取圖像
image = cv2.imread('your_image.jpg')

# 將圖像旋轉90度
rotated_image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)

# 顯示旋轉後的圖像
cv2.imshow('Rotated Image', rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

翻轉圖片的方法

cv2.flip() 是 OpenCV 中用於圖像翻轉的函數。它可以在水平、垂直或兩個方向上翻轉圖像。該函數接受三個參數:輸入圖像、翻轉的模式和輸出圖像的可選參數。

dst = cv2.flip(src, flipCode[, dst])

flipCode:翻轉的模式。可以是以下值之一:

  • 0:水平翻轉(沿著垂直軸翻轉)。
  • 1:垂直翻轉(沿著水平軸翻轉)。
  • -1:同時在水平和垂直方向上翻轉。

cv2.flip() 函數和 cv2.rotate() 函數都可以用於實現圖像的旋轉和翻轉,但它們的效果是不同的。

cv2.flip() 函數可以在水平和垂直方向上翻轉圖像,包括水平翻轉、垂直翻轉和同時在水平和垂直方向上翻轉。例如,使用 cv2.flip(image, -1) 可以同時在水平和垂直方向上翻轉圖像。

cv2.rotate() 函數用於對圖像進行旋轉。通過指定旋轉的角度和旋轉中心點,可以實現不同角度的旋轉。例如,使用 cv2.rotate(image, cv2.ROTATE_180_CLOCKWISE) 可以將圖像順時針旋轉180度。

雖然cv2.flip(image, -1)cv2.rotate(image, cv2.ROTATE_180_CLOCKWISE) 可以實現類似的效果,將圖像翻轉或旋轉180度,但它們的內部操作是不同的。 cv2.flip() 是基於軸對稱翻轉實現的,而 cv2.rotate() 是基於旋轉變換實現的。

針對形狀做角度校正

在許多圖像偵測的狀況,我們仍然會需要針對物件去做旋轉,首先我們一定是先用cv2.findContours取得輪廓,然後取得該物件輪廓的角度。這邊很重要的,就是要取得物件輪廓的角度,要取得角度,首先就要先去做輪廓擬合(請參考: OpenCV裡面形狀擬合的幾種方法)。

這邊我大推使用橢圓去做輪廓擬合並且取得軸心的角度,為什麼呢? 雖然cv2.minAreaRect() 可計算最小擬合矩形,但是這個矩形會非常容易受到輪廓的些微影響而改變擬合的方式,例如以下圖為例,就有可能有黑框、紅色框兩種的最小擬合矩形(會視當下輪廓取得的細微變化而改變)。也因此所取得的角度會非常多變,後續的辨識也會更困難

但是使用最小擬合橢圓,對於像上面這種左右、上下為對稱,但是長寬不同的形狀來說,非常適合使用最小擬合橢圓cv2.fitEllipse(),使用範例如下

import cv2

image = cv2.imread('./333_2023-06-08_19-57-30.jpg')
canny = cv2.Canny(image , 50, 250)
cnts, hier = cv2.findContours(canny , cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 執行最小橢圓擬合
ellipse = cv2.fitEllipse(cnts[0])
(center, axes, angle) = ellipse
cv2.ellipse(image, ellipse, (0, 255, 0), 2)
# 顯示結果
cv2.imshow('image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

上面可以看到(center, axes, angle) = ellipse這邊的angle就是所偵測到的輪廓的角度,接著可以用cv2.warpAffine方法將圖像轉正

def rotatedDice(image, cnt):
    # 取得最小擬合橢圓並對圖像做翻轉
    ellipse = cv2.fitEllipse(cnt)
    (center, axes, angle) = ellipse
    angle = angle + 90
    rotation_matrix = cv2.getRotationMatrix2D(tuple(center), angle, 1)
    image = cv2.warpAffine(image, rotation_matrix,(image.shape[1], image.shape[0]))
    # 計算裁切位置
    mark = np.zeros_like(image)
    cv2.drawContours(mark, [cnt], 0, (255, 255, 255), -1)
    mark = cv2.warpAffine(mark, rotation_matrix,(mark.shape[1], mark.shape[0]))
    mark = cv2.cvtColor(mark, cv2.COLOR_RGB2GRAY)
    cnts, hier = cv2.findContours(mark, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    x, y, w, h = cv2.boundingRect(cnts[0])
    matting_result = image[y:y+h,x:x+w,:]
    return matting_result

從上面,我們使用cv2.warpAffine來做圖片角度的校正,warpAffine裡面有要輸入一個旋轉的矩陣的參數,在上面的範例,我們使用cv2.getRotationMatrix2D,這個參數是單純做形狀旋轉,但是在真實的世界當中,大部分3D的角度轉換也會帶有著深度的轉換,如下圖

這時候就會需要使用cv2.getAffineTransform來取得這個旋轉矩陣

import cv2
import numpy as np

# 定義三個點的坐標
point1 = (106, 92)
point2 = (28, 91)
point3 = (154, 33)

# 定義旋轉角度
rotation_angle = -45

# 創建一個空白圖像
image = np.zeros((500, 500), dtype=np.uint8)

# 在圖像上繪製三角形
cv2.drawContours(image, [np.array([point1, point2, point3])], 0, (255), thickness=2)

# 計算旋轉中心
center = np.mean([point1, point2, point3], axis=0)

# 構建旋轉矩陣
rotation_matrix = cv2.getRotationMatrix2D(tuple(center), rotation_angle, 1)

# 對整個圖像進行旋轉
rotated_image = cv2.warpAffine(image, rotation_matrix, (image.shape[1], image.shape[0]))

# 顯示旋轉後的圖像
cv2.imshow("Rotated Image", rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Posted on

限制ffmpeg初始連接的時間

ffmpeg中的analyzeduration和probesize

在FFmpeg中,-analyzeduration和-probesize是用於設置媒體分析的參數。

-analyzeduration參數用於指定分析媒體文件的持續時間。當你在FFmpeg中打開一個媒體文件時,它需要一些時間來分析文件的內容,以確定其格式、編解碼器和其他相關的信息。這個參數設置了分析的時間長度。較長的-analyzeduration值可能會導致更準確的分析結果,但同時也會增加打開文件的時間。預設值為5,000,000微秒(5秒)。

-probesize參數用於指定分析媒體文件時讀取的數據大小。當FFmpeg分析媒體文件時,它會從文件中讀取一些數據並進行分析。這個參數設置了從媒體文件中讀取的數據大小。較大的-probesize值可能會導致更準確的分析結果,但同時也會增加分析的時間和記憶體使用量。預設值為50,000字節。

前置metadata

播放器在網絡點播場景下去請求MP4 視頻數據,需要先獲取到文件的metadata,解析出該文件的編碼、幀率等信息後才能開始邊下邊播。如果MP4 的metadata 數據塊被編碼在文件尾部,這種情況會導致播放器只有下載完整個文件後才能成功解析並播放這個視頻。對於這種視頻,我們最好能夠在服務端將其重新編碼,將metadata 數據塊轉移到靠近文件頭部的位置,保證播放器在線請求時能較快播放。比如FFmpeg 的下列命令就可以支持這個操作:

ffmpeg -i bad.mp4 -movflags faststart good.mp4

控制讀取的數據量大小

在外部可以通過設置 probesize 和 analyzeduration 兩個參數來控制該函數讀取的數據量大小和分析時長為比較小的值來降低 avformat_find_stream_info 的耗時,從而優化播放器首屏秒開。但是,需要注意的是這兩個參數設置過小時,可能會造成預讀數據不足,無法解析出碼流信息,從而導致播放失敗、無音頻或無視頻的情況。所以,在服務端對視頻格式進行標準化轉碼,從而確定視頻格式,進而再去推算 avformat_find_stream_info 分析碼流信息所兼容的最小的 probesize 和analyzeduration,就能在保證播放成功率的情況下最大限度地區優化首屏秒開。

probesize 和 analyzeduration太短的可能影響


如果將-probesize和-analyzeduration設置得太短,可能會導致以下問題:

  • 不準確的媒體分析:probesize和analyzeduration參數用於指定媒體分析的數據大小和時間長度。如果這兩個值設置得太短,FFmpeg可能無法讀取足夠的數據或分析足夠長的時間,從而導致分析結果的不準確性。這可能會影響到媒體文件的正確解碼、格式識別和相關信息的獲取。
  • 遺漏關鍵信息:媒體文件中的關鍵信息通常在文件的早期部分或特定位置。如果probesize和analyzeduration設置得太短,FFmpeg可能無法讀取到這些關鍵信息,進而影響解碼、播放或處理過程的正確性和完整性。
  • 性能問題:probesize和analyzeduration參數的值也會影響處理媒體文件所需的時間和資源。如果值設置得太短,FFmpeg可能需要更頻繁地從媒體文件中讀取數據或進行分析,增加了I/O操作和CPU負載,進而導致性能下降。
Posted on

為影片產生會議紀錄及重點擷取

將影片轉為MP3

先照這篇的方式安裝FFMPEG,接著就可以使用ffmpeg將影片轉成mp3檔案

ffmpeg -i input.mp4 -vn -acodec libmp3lame output.mp3

在上述命令中,input.mp4是輸入的MP4文件路徑,output.mp3是輸出的MP3文件路徑。

從語音檔案使用AI提取文字

這個功能在WORD就有了,若是沒有WORD,GOOGLE文件也有相似的聽寫功能,以下為我使用Office內建聽寫功能的示範

先使用轉錄功能

接著選擇輸入語言為台灣國語,並上傳剛剛擷取出來的mp3檔案

選擇完檔案會開始上傳MP3並且擷取音檔內的文字,這也是為什麼一開始我會希望將mp4轉成mp3,因為含影像的檔案較大,純音檔較小,上傳較小的檔案這邊所花費的時間會少一點

當節錄文字完成後,選擇將文字加到檔案內,就會出現如下的語音謄錄文字

讓文字更易懂

使用工具: ChatGPT

一直到這邊所產生的文字,都很不容易讓人理解,因為所擷取出的文字很容易會有錯別字,例如: 【視障小孩】可能會被聽寫成【師丈小孩】,根本意義完全不同,讓人難以理解。

但是ChatGPT對於理解這樣的錯別字,比對上下文去猜出正確辭意的能力頗強,所以可以使用ChatGPT請他幫忙整理內容

例如上面的文字GTP所整理出的內容如下

接著再重複使用上面產生的內容,請GPT產生摘要、標題,我們只需要作內容審核、確認、修正即可,可以大幅節省人力唷!

另外,對CHATGPT所下的指令也會影響到產出,例如上面我使用【順成文章】的指令,所以最後面CHATGPT就自己唬爛了一些不相關的內容(甚麼不僅僅是個人問題之類的老師根本沒有講)。這時候就可以改使用【順過讓文字更好讀】這樣的指令,就比較不會產生不相關的內容。

建議可以多嘗試幾種不同的指令,直接針對他所整理過的不滿意的方向請她重新整理,直到CHATGPT給出較滿意的產出後,再自行做驗證/整理