Python數據處理入門
常用庫學習
numpy
NumPy(Numerical Python) 是 Python 中用于高效數值計算的庫,核心是提供一個強大的 ndarray
?(多維數組)對象,類似于 C/C++ 中的數組,但支持更豐富的操作,比如切片、廣播、線性代數等。
基本用法:
import numpy as np
創建數組
a = np.array([1, 2, 3]) # 一維數組
b = np.array([[1, 2], [3, 4]]) # 二維數組print(a) # [1 2 3]
print(b) # [[1 2]# [3 4]]
說明:使用 np.array()
? 可以把列表或嵌套列表轉換為 NumPy 數組。
查看數組形狀和屬性
print(a.shape) # (3,) → 一維數組,3個元素
print(b.shape) # (2, 2) → 2行2列print(b.ndim) # 2 → 二維數組
print(b.dtype) # int64 → 元素類型
常用數組創建方法
print(np.zeros((2, 3))) # 全0數組
# [[0. 0. 0.]
# [0. 0. 0.]]print(np.ones((2, 2))) # 全1數組
# [[1. 1.]
# [1. 1.]]print(np.eye(3)) # 單位矩陣
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]print(np.arange(0, 5, 1)) # 等差數組:[0 1 2 3 4]
print(np.linspace(0, 1, 5)) # 等間距:[0. 0.25 0.5 0.75 1.]
數組運算
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])print(a + b) # [5 7 9]
print(a - b) # [-3 -3 -3]
print(a * 2) # [2 4 6]
print(a ** 2) # [1 4 9]
說明:NumPy 支持逐元素運算,不需要寫循環。
數組統計函數
a = np.array([1, 2, 3, 4])print(np.sum(a)) # 總和:10
print(np.mean(a)) # 平均數:2.5
print(np.max(a)) # 最大值:4
print(np.min(a)) # 最小值:1
print(np.std(a)) # 標準差
數組索引和切片
a = np.array([[10, 20, 30],[40, 50, 60]])print(a[0, 1]) # 第1行第2列 → 20
print(a[1]) # 第2行 → [40 50 60]
print(a[:, 0]) # 第1列 → [10 40]
說明:二維數組使用 a[行, 列]
? 方式訪問。
形狀變換
a = np.arange(6) # [0 1 2 3 4 5]
b = a.reshape((2, 3)) # 改成2行3列print(b)
# [[0 1 2]
# [3 4 5]]
廣播機制(自動對齊維度)
a = np.array([[1, 2], [3, 4]])
b = np.array([10, 20])print(a + b)
# [[11 22]
# [13 24]]
說明:b
? 自動擴展為二維數組 [10, 20]
?,重復到每一行。
矩陣乘法 vs 元素乘法
a = np.array([[1, 2],[3, 4]])
b = np.array([[5, 6],[7, 8]])print(a * b) # 元素乘法
# [[ 5 12]
# [21 32]]print(np.dot(a, b)) # 矩陣乘法
# [[19 22]
# [43 50]]
數組條件篩選
a = np.array([1, 2, 3, 4, 5])
print(a[a > 3]) # [4 5]
復制與原地修改
a = np.array([1, 2, 3])
b = a.copy() # 深拷貝,不影響原數組
b[0] = 99print(a) # [1 2 3]
print(b) # [99 2 3]
?
pandas
Pandas(Python Data Analysis Library) 是 Python 中用于 數據分析與處理 的核心庫,提供了強大的 DataFrame
? 和 Series
? 兩種數據結構,適用于結構化數據(表格、Excel、數據庫)的讀取、清洗、分析、可視化等。
基本用法:
import pandas as pd
創建 Series(一維數據)
s = pd.Series([10, 20, 30, 40])
print(s)
# 0 10
# 1 20
# 2 30
# 3 40
# dtype: int64
說明:Series
? 是帶標簽的一維數組,默認索引為 0 開始的整數。
創建 DataFrame(二維表格)
data = {'name': ['Alice', 'Bob', 'Charlie'],'age': [25, 30, 35],'city': ['NY', 'LA', 'Chicago']}df = pd.DataFrame(data)
print(df)
# name age city
# 0 Alice 25 NY
# 1 Bob 30 LA
# 2 Charlie 35 Chicago
說明:DataFrame
? 是 Pandas 的核心表格型結構,類似于 Excel 表。
查看數據基本信息
print(df.shape) # (3, 3) → 3行3列
print(df.columns) # 列名:Index(['name', 'age', 'city'], dtype='object')
print(df.index) # 行索引:RangeIndex(start=0, stop=3, step=1)
print(df.dtypes) # 每列的數據類型
讀取常見文件
# 讀取 CSV 文件
df = pd.read_csv('data.csv')# 讀取 Excel 文件
df = pd.read_excel('data.xlsx')# 保存為 CSV 文件
df.to_csv('output.csv', index=False)
訪問列與行
print(df['name']) # 訪問單列(Series)
print(df[['name', 'age']]) # 多列(DataFrame)print(df.loc[0]) # 按標簽訪問第1行
print(df.iloc[1]) # 按位置訪問第2行
說明:loc
? 用標簽,iloc
? 用索引位置。
條件篩選
print(df[df['age'] > 28])
# 篩選出 age > 28 的行
添加、修改與刪除列
df['salary'] = [5000, 6000, 7000] # 添加新列print(df['age'] * 2) # 修改方式:表達式df.drop('city', axis=1, inplace=True) # 刪除列
缺失值處理
df = pd.DataFrame({'name': ['Alice', 'Bob', None],'age': [25, None, 35]
})print(df.isnull()) # 判斷是否為空
print(df.dropna()) # 刪除含缺失值的行
print(df.fillna(0)) # 填充缺失值
分組與聚合
df = pd.DataFrame({'dept': ['IT', 'HR', 'IT', 'HR'],'salary': [6000, 5000, 7000, 5500]
})print(df.groupby('dept').mean())
# 按部門求平均工資
排序與唯一值
print(df.sort_values('salary', ascending=False)) # 按工資降序
print(df['dept'].unique()) # 唯一值
合并與拼接
df1 = pd.DataFrame({'id': [1, 2], 'name': ['Alice', 'Bob']})
df2 = pd.DataFrame({'id': [1, 2], 'score': [90, 85]})print(pd.merge(df1, df2, on='id')) # 按 id 合并
應用函數與映射
df['age_group'] = df['age'].apply(lambda x: 'adult' if x >= 30 else 'young')
print(df)
說明:使用 apply()
? 可以對每個元素應用函數。
導出結果
df.to_csv('result.csv', index=False)
df.to_excel('result.xlsx', index=False)
json
json(JavaScript Object Notation) 是一種輕量級的數據交換格式,Python 內置了 json
? 模塊來方便地進行 JSON 數據的解析和生成,常用于數據持久化、前后端通信等場景。
基本用法:
import json
Python 與 JSON 的對應關系:
Python 類型 | JSON 類型 |
---|---|
?dict ? | object |
?list ?、tuple ? | array |
?str ? | string |
?int ?/float ? | number |
?True ?/False ? | true / false |
?None ? | null |
Python 轉 JSON 字符串(序列化)
data = {"name": "Alice", "age": 25, "is_student": False}
json_str = json.dumps(data)print(json_str)
# {"name": "Alice", "age": 25, "is_student": false}
說明:json.dumps()
? 可以把 Python 對象轉換為 JSON 字符串。
JSON 字符串轉 Python 對象(反序列化)
json_str = '{"name": "Alice", "age": 25, "is_student": false}'
data = json.loads(json_str)print(data)
# {'name': 'Alice', 'age': 25, 'is_student': False}
說明:json.loads()
? 可以把 JSON 字符串解析為 Python 對象。
序列化時格式化輸出
data = {"name": "Bob", "scores": [90, 85, 88]}
print(json.dumps(data, indent=2))
# {
# "name": "Bob",
# "scores": [
# 90,
# 85,
# 88
# ]
# }
說明:通過 indent
? 參數可控制縮進,增加可讀性。
中文處理
data = {"name": "小明", "age": 18}
print(json.dumps(data, ensure_ascii=False))
# {"name": "小明", "age": 18}
說明:默認中文會轉成 Unicode,用 ensure_ascii=False
? 可以保留中文。
寫入 JSON 文件
data = {"title": "Python", "level": "beginner"}with open("data.json", "w", encoding="utf-8") as f:json.dump(data, f, ensure_ascii=False, indent=2)使用 with open(...) 打開一個文件:"data.json":要寫入的文件名(如果沒有會自動創建)"w":寫入模式(write),會覆蓋原有內容encoding="utf-8":指定編碼為 UTF-8,確保中文不會亂碼f 是文件對象,代表這個打開的文件
說明:json.dump()
? 將 Python 對象寫入文件,支持格式化輸出。
從 JSON 文件讀取
with open("data.json", "r", encoding="utf-8") as f:data = json.load(f)print(data)
說明:json.load()
? 用于從文件中讀取并解析 JSON 數據。
復雜嵌套結構解析
json_str = '''
{"user": {"name": "Tom","skills": ["Python", "C++"]}
}
'''data = json.loads(json_str)
print(data["user"]["skills"][0]) # Python
說明:嵌套結構可通過多級鍵訪問。
轉換時處理非默認類型
import datetimedef custom(obj):if isinstance(obj, datetime.datetime):return obj.isoformat()now = datetime.datetime.now()
print(json.dumps({"time": now}, default=custom))
# {"time": "2025-07-28T11:30:00.123456"}
說明:使用 default
? 參數可以處理自定義類型。
字符串與字典互轉小技巧
s = '{"x": 1, "y": 2}'
d = json.loads(s)
s2 = json.dumps(d)print(type(d)) # <class 'dict'>
print(type(s2)) # <class 'str'>
防止類型錯誤
# 錯誤示例:集合不是 JSON 可序列化類型
data = {"nums": {1, 2, 3}}
# json.dumps(data) 會報錯# 解決方式:轉換為 list
data["nums"] = list(data["nums"])
print(json.dumps(data))
說明:json
? 只支持部分 Python 類型,需提前轉換。
與字典深拷貝的配合
import copy
original = {"a": 1, "b": [1, 2]}# 用 json 序列化方式做深拷貝
clone = json.loads(json.dumps(original))clone["b"][0] = 999
print(original) # {'a': 1, 'b': [1, 2]}
print(clone) # {'a': 1, 'b': [999, 2]}
說明:json
? 也可以作為一種簡易深拷貝手段(前提是可序列化)。
PIL / Pillow
Pillow(PIL 的分支) 是 Python 中用于圖像處理的強大庫,支持打開、編輯、保存多種格式的圖片。Pillow 是原始 PIL 庫的增強版,常用于圖像縮放、裁剪、轉換、繪圖等操作。
基本用法:
from PIL import Image
打開和顯示圖片
img = Image.open("example.jpg") # 打開圖片
img.show() # 使用默認圖片查看器顯示
說明:使用 Image.open()
? 加載本地圖片,show()
? 會調用系統圖片查看器。
查看圖片屬性
print(img.format) # 圖片格式,如 JPEG
print(img.size) # 尺寸:如 (寬, 高)
print(img.mode) # 模式:如 RGB、L、RGBA
保存圖片
img.save("output.png") # 另存為 PNG 格式
說明:可以將圖片保存為不同格式,只需更改文件后綴。
圖像轉換
gray = img.convert("L") # 轉為灰度圖
rgba = img.convert("RGBA") # 轉為含透明通道
說明:使用 convert()
? 可以轉換圖片顏色模式。
圖像縮放和縮略圖
resized = img.resize((100, 100)) # 強制縮放為100x100thumb = img.copy()
thumb.thumbnail((100, 100)) # 縮略圖,保持比例
說明:resize()
? 會強行變形,thumbnail()
? 則保持原比例縮小。
裁剪圖像
box = (50, 50, 200, 200) # 左、上、右、下坐標
cropped = img.crop(box)
cropped.show()
說明:裁剪區域的坐標單位為像素,左上角為原點 (0, 0)。
旋轉和翻轉
rotated = img.rotate(90) # 順時針旋轉90°
flipped = img.transpose(Image.FLIP_LEFT_RIGHT) # 水平翻轉
說明:rotate()
? 默認逆時針,實際顯示是順時針;transpose()
? 支持翻轉和旋轉。
疊加文字(繪圖)
from PIL import ImageDraw, ImageFontdraw = ImageDraw.Draw(img)
draw.text((10, 10), "Hello", fill="red")
img.show()
說明:使用 ImageDraw.Draw()
? 可對圖像進行繪制,默認字體簡單,若需設置字體需加載 ImageFont
?。
拼接圖片
img1 = Image.open("a.jpg")
img2 = Image.open("b.jpg")new_img = Image.new("RGB", (img1.width + img2.width, img1.height))
new_img.paste(img1, (0, 0))
new_img.paste(img2, (img1.width, 0))
new_img.show()
說明:通過創建新圖像并粘貼已有圖像,可以實現拼接。
獲取像素值和修改像素
pixel = img.getpixel((0, 0)) # 獲取坐標(0,0)處像素
img.putpixel((0, 0), (255, 0, 0)) # 修改為紅色像素(RGB 模式)
說明:適用于手動像素級操作,效率較低。
圖片轉 numpy 數組
import numpy as nparr = np.array(img)
print(arr.shape) # 如:(高, 寬, 通道數)
說明:配合 NumPy 可以進行高效的圖像計算與分析。
numpy 數組轉圖片
new_img = Image.fromarray(arr)
new_img.show()
說明:Image.fromarray()
? 可以把 NumPy 數組還原為圖像對象。
圖像格式轉換與壓縮
img.save("output.jpg", quality=85) # 保存為 JPEG 并設置壓縮質量
說明:quality
? 參數可控制 JPEG 圖像壓縮程度,范圍 1~95(默認是 75)。
檢查圖片是否損壞
try:img.verify()print("圖片無損壞")
except:print("圖片損壞")
說明:verify()
? 方法可以驗證圖片文件是否完整有效。
創建純色圖像
new_img = Image.new("RGB", (200, 200), color="blue")
new_img.show()
說明:Image.new()
? 可創建指定顏色和尺寸的新圖片。
opencv
OpenCV(Open Source Computer Vision Library) 是一個開源的計算機視覺庫,廣泛用于圖像處理、視頻分析、人臉識別等任務。Python 中使用時通過 cv2
? 模塊操作,支持 NumPy 數組與圖像的高效互操作。
基本用法:
import cv2
讀取和顯示圖片
img = cv2.imread("a.jpg") # 讀取圖像
cv2.imshow("Image", img) # 顯示圖像窗口
cv2.waitKey(0) # 等待按鍵
cv2.destroyAllWindows() # 關閉窗口
說明:OpenCV 使用 BGR
? 而非 RGB
?;必須調用 waitKey()
? 才能顯示窗口。
保存圖片
cv2.imwrite("output.jpg", img)
說明:將圖像保存為文件,支持 jpg/png 等格式。
圖像尺寸和屬性
print(img.shape) # (高, 寬, 通道數),例如 (400, 600, 3)
print(img.dtype) # 圖像數據類型,例如 uint8
修改圖像尺寸
resized = cv2.resize(img, (200, 100)) # 寬200,高100
轉換顏色空間
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 轉灰度圖
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR轉RGB
圖像裁剪與ROI
roi = img[100:200, 150:300] # 裁剪區域:高100-200,寬150-300
說明:和 NumPy 一樣用切片操作,裁剪結果仍是圖像。
圖像繪圖(在圖上畫圖形)
cv2.rectangle(img, (50, 50), (150, 150), (0, 255, 0), 2) # 綠色矩形
cv2.circle(img, (100, 100), 30, (255, 0, 0), -1) # 藍色實心圓
cv2.line(img, (0, 0), (200, 200), (0, 0, 255), 3) # 紅色直線
添加文字
cv2.putText(img, "Hello", (50, 50), cv2.FONT_HERSHEY_SIMPLEX,1, (255, 255, 255), 2)
說明:可以自定義字體、大小、顏色和粗細。
圖像濾波(模糊)
blur = cv2.GaussianBlur(img, (5, 5), 0) # 高斯模糊
median = cv2.medianBlur(img, 5) # 中值模糊
邊緣檢測
edges = cv2.Canny(img, 100, 200) # 邊緣檢測
說明:兩個參數為低/高閾值。
圖像閾值處理(二值化)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
圖像按位操作(遮罩)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
mask[100:200, 100:200] = 255
masked = cv2.bitwise_and(img, img, mask=mask)
攝像頭讀取(實時視頻)
cap = cv2.VideoCapture(0) # 0代表默認攝像頭while True:ret, frame = cap.read()if not ret:breakcv2.imshow("Live", frame)if cv2.waitKey(1) == ord("q"):breakcap.release()
cv2.destroyAllWindows()
說明:按下 q
? 鍵退出循環。
圖像通道操作
b, g, r = cv2.split(img) # 拆分BGR通道
merged = cv2.merge([b, g, r]) # 合并通道
圖像疊加(加權合并)
blended = cv2.addWeighted(img1, 0.6, img2, 0.4, 0)
說明:將兩張圖按比例混合,適用于圖像融合、水印等。
輪廓檢測(基本)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
保存視頻
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('out.avi', fourcc, 20.0, (640, 480))while True:ret, frame = cap.read()if not ret:breakout.write(frame)cv2.imshow('Recording', frame)if cv2.waitKey(1) == ord('q'):breakcap.release()
out.release()
cv2.destroyAllWindows()
復制與原地修改
img_copy = img.copy()
img_copy[0:100, 0:100] = 0 # 修改左上角為黑色,不影響原圖
任務一實踐
任務:
通過編寫腳本,處理DDXPlus數據集,將數據集release_train_patients中每個病人(案例)劃分為一個json文件
(1)release_train_patients
(2)release_evidences.json(里面含有代碼(E_91等)的映射關系)
(3)release_conditions.json(里面有每個疾病的具體信息)
劃分的json文件,要包含上述三個文件中的相應信息。
json文件命名格式participant_{i}.json
因為train數據集的病人比較多,劃分出200個病人的即可
├── data/
│ └── DDXPlus/
│ ├── release_evidences.json
│ ├── release_conditions.json
│ └── release_train_patients.csv
├── result/ ← 希望輸出的 json 文件保存到這里result/participants_output
├── split_ddxplus.py ← 要運行的腳本
創建虛擬環境
python -m venv venv激活虛擬環境
.\.venv\Scripts\Activate.ps1安裝pandas庫
pip install pandas
split_ddxplus.py腳本源碼學習
import json # 導入json模塊,用于JSON數據的讀取和寫入
import os # 導入os模塊,用于文件和目錄操作
import pandas as pd # 導入pandas模塊,用于讀取和處理CSV文件# 設置基礎路徑,即數據文件所在目錄
base_path = 'data/DDXPlus'# 構造訓練數據CSV文件的完整路徑
train_file = os.path.join(base_path, 'release_train_patients.csv')# 構造證據JSON文件的完整路徑
evidence_file = os.path.join(base_path, 'release_evidences.json')# 構造病癥條件JSON文件的完整路徑
condition_file = os.path.join(base_path, 'release_conditions.json')# 設置結果輸出目錄
output_dir = 'result/participants_output'# 創建輸出目錄,如果目錄已存在則不會報錯
os.makedirs(output_dir, exist_ok=True)# 讀取訓練數據CSV文件,結果是pandas的DataFrame對象
train_df = pd.read_csv(train_file)# 以utf-8編碼打開證據JSON文件,讀取內容為Python對象(一般是list或dict)
with open(evidence_file, 'r', encoding='utf-8') as f:evidence_data = json.load(f)# 判斷evidence_data的類型,確保它是一個字典(方便后續按ID查找)
if isinstance(evidence_data, list):# 如果是列表,轉換為字典,鍵為每個證據的'id',值為該證據的完整信息evidence_map = {item['id']: item for item in evidence_data}
elif isinstance(evidence_data, dict):# 如果本來就是字典,直接賦值evidence_map = evidence_data
else:# 如果既不是list也不是dict,拋出異常提示格式不支持raise ValueError("release_evidences.json 格式不支持")# 以utf-8編碼打開條件JSON文件,讀取內容為Python對象
with open(condition_file, 'r', encoding='utf-8') as f:condition_data = json.load(f)# 同樣判斷condition_data類型,確保是字典格式
if isinstance(condition_data, list):# 如果是列表,轉換為字典,鍵為病癥id,值為病癥詳細信息condition_map = {item['id']: item for item in condition_data}
elif isinstance(condition_data, dict):# 直接賦值condition_map = condition_data
else:# 拋異常raise ValueError("release_conditions.json 格式不支持")# 遍歷訓練數據的前200個病人記錄(防止數據過多,控制處理數量)
for i in range(min(200, len(train_df))):# 通過iloc根據索引i選取DataFrame中的一行數據,返回Series對象row = train_df.iloc[i]# 解析該行中'evidence'字段的JSON字符串,轉換成Python對象try:# 先判斷'evidence'字段是否存在且不是空值,再用json.loads轉換if 'evidence' in row and pd.notna(row['evidence']):patient_evidences_raw = json.loads(row['evidence'])else:patient_evidences_raw = []except:# 如果解析失敗,則賦空列表,避免程序崩潰patient_evidences_raw = []# 同理解析'conditions'字段try:if 'conditions' in row and pd.notna(row['conditions']):patient_conditions_raw = json.loads(row['conditions'])else:patient_conditions_raw = []except:patient_conditions_raw = []# 從解析后的evidences列表中提取每條證據的id,過濾掉格式不對的數據evidence_ids = [e['id'] for e in patient_evidences_raw if isinstance(e, dict) and 'id' in e]# 確保patient_conditions_raw是列表類型,賦給condition_ids,否則賦空列表condition_ids = patient_conditions_raw if isinstance(patient_conditions_raw, list) else []# 根據id從證據字典中獲取對應證據詳細信息,忽略id不存在的情況evidence_details = [evidence_map[eid] for eid in evidence_ids if eid in evidence_map]# 根據id從條件字典中獲取對應條件詳細信息,忽略id不存在的情況condition_details = [condition_map[cid] for cid in condition_ids if cid in condition_map]# 將當前病人整行信息轉換為字典形式,方便一起存儲patient_info = row.to_dict()# 構造完整的病人數據結構,包括基本信息,證據詳情,條件詳情result = {'patient_info': patient_info,'evidence_details': evidence_details,'condition_details': condition_details}# 生成輸出文件名,例如 participant_0.jsonfilename = f'participant_{i}.json'# 拼接文件保存完整路徑filepath = os.path.join(output_dir, filename)# 以寫入模式打開文件,編碼為utf-8with open(filepath, 'w', encoding='utf-8') as f:# 將result字典轉換成格式化的JSON字符串寫入文件,確保中文正常顯示json.dump(result, f, ensure_ascii=False, indent=2)# 打印提示,表示當前病人數據已成功保存print(f"已保存:{filename}")
?
?
詳細分步解析
import json
import os
import pandas as pd
- 導入 Python 標準庫中的
json
? 用于 JSON 格式數據處理,os
? 用于操作文件路徑和目錄,pandas
? 用于處理 CSV 文件和表格數據。
# 設置路徑
base_path = 'data/DDXPlus'
train_file = os.path.join(base_path, 'release_train_patients.csv')
evidence_file = os.path.join(base_path, 'release_evidences.json')
condition_file = os.path.join(base_path, 'release_conditions.json')
- ?
base_path
? 是數據集文件所在的根目錄。 - 使用
os.path.join
? 拼接得到訓練數據 CSV 文件的完整路徑train_file
?。 - 同理,拼接得到
evidence_file
? 和condition_file
? 的路徑,分別對應證據和病癥的 JSON 文件。
# 輸出目錄
output_dir = 'result/participants_output'
os.makedirs(output_dir, exist_ok=True)
- 定義輸出結果保存目錄
output_dir
?。 - ?
os.makedirs
? 遞歸創建該目錄,如果目錄已存在則不會報錯(exist_ok=True
?)。
# 讀取 CSV 數據
train_df = pd.read_csv(train_file)
- 用
pandas.read_csv
? 讀取訓練數據 CSV 文件,存成一個 DataFrame 對象train_df
?,方便后續按行操作。
# 讀取 evidence JSON 數據
with open(evidence_file, 'r', encoding='utf-8') as f:evidence_data = json.load(f)
- 以 UTF-8 編碼打開證據 JSON 文件,使用
json.load
? 讀取成 Python 對象,賦值給evidence_data
?。
# 判斷 evidence_data 是不是 dict,不是就轉換為 dict
if isinstance(evidence_data, list):evidence_map = {item['id']: item for item in evidence_data}
elif isinstance(evidence_data, dict):evidence_map = evidence_data
else:raise ValueError("release_evidences.json 格式不支持")
-
判斷
evidence_data
? 的數據類型:- 如果是列表,則把每個證據的
id
? 作為 key,證據對象作為 value,構建成字典evidence_map
? 方便快速查找。 - 如果本身是字典,直接賦值給
evidence_map
?。 - 其它類型則拋出異常,提示格式不支持。
- 如果是列表,則把每個證據的
# 讀取 condition JSON 數據
with open(condition_file, 'r', encoding='utf-8') as f:condition_data = json.load(f)
- 以 UTF-8 編碼打開疾病條件 JSON 文件,讀取為 Python 對象
condition_data
?。
# 同樣處理 condition_data
if isinstance(condition_data, list):condition_map = {item['id']: item for item in condition_data}
elif isinstance(condition_data, dict):condition_map = condition_data
else:raise ValueError("release_conditions.json 格式不支持")
- 對
condition_data
? 做同樣的判斷和轉換,確保最終condition_map
? 是字典,方便后續根據 ID 查找詳細信息。
# 處理前200個病人
for i in range(min(200, len(train_df))):row = train_df.iloc[i]
- 循環遍歷訓練數據的前 200 條記錄(如果不足200條,就遍歷所有)。
- ?
train_df.iloc[i]
? 按索引 i 獲取對應行數據,存到row
?,類型是 pandas Series。
try:patient_evidences_raw = json.loads(row['evidence']) if 'evidence' in row and pd.notna(row['evidence']) else []except:patient_evidences_raw = []
-
嘗試解析當前行的
'evidence'
? 字段(應該是 JSON 字符串):- 如果字段存在且不是空值,使用
json.loads
? 轉成 Python 對象。 - 如果不存在或是空,賦空列表。
- 若解析失敗(格式錯誤等),也賦空列表,避免程序崩潰。
- 如果字段存在且不是空值,使用
try:patient_conditions_raw = json.loads(row['conditions']) if 'conditions' in row and pd.notna(row['conditions']) else []except:patient_conditions_raw = []
- 同理,嘗試解析當前行的
'conditions'
? 字段,處理方法和上一段相同,得到原始條件列表patient_conditions_raw
?。
# 提取 IDevidence_ids = [e['id'] for e in patient_evidences_raw if isinstance(e, dict) and 'id' in e]condition_ids = patient_conditions_raw if isinstance(patient_conditions_raw, list) else []
- 從
patient_evidences_raw
? 中篩選出每個證據的id
? 字段,生成evidence_ids
? 列表。 - ?
patient_conditions_raw
? 如果是列表則直接賦值給condition_ids
?,否則賦空列表(保險處理)。
# 獲取詳細信息evidence_details = [evidence_map[eid] for eid in evidence_ids if eid in evidence_map]condition_details = [condition_map[cid] for cid in condition_ids if cid in condition_map]
- 根據
evidence_ids
? 和condition_ids
?,從之前構建的映射字典evidence_map
? 和condition_map
? 中取對應詳細信息,生成詳細信息列表。
# 構建結果patient_info = row.to_dict()
- 將當前患者行數據
row
? 轉成字典,方便和證據、條件信息合并,構成完整數據結構。
result = {'patient_info': patient_info,'evidence_details': evidence_details,'condition_details': condition_details}
- 構造最終輸出的字典結構,包含患者信息,證據詳情和條件詳情三個部分。
# 保存文件filename = f'participant_{i}.json'filepath = os.path.join(output_dir, filename)with open(filepath, 'w', encoding='utf-8') as f:json.dump(result, f, ensure_ascii=False, indent=2)
- 構造輸出文件名,格式是
participant_0.json
?、participant_1.json
? 等。 - 拼接成完整保存路徑。
- 以 UTF-8 編碼寫入 JSON 文件,參數
ensure_ascii=False
? 保持中文顯示,indent=2
? 格式化縮進方便閱讀。
print(f"已保存:{filename}")
- 控制臺打印提示,告訴用戶當前第 i 個患者信息已保存完成,方便調試和跟蹤進度。
任務二實踐
任務:
處理vaihingen數據集,將每個大圖像劃分并轉換為512**512的jpg格式圖像,不足512** 512的部分用黑色像素填充,圖像和其標簽要一起進行處理
項目根目錄/
├── data/
│ └── vaihingen/
│ ├── image/ ← 存放原始圖像(.tif)
│ └── label/ ← 存放對應標簽圖(.tif)
├── result/
│ └── new_vaihingen/
│ ├── new_image/ ← 保存裁剪后的圖像(.jpg)
│ └── new_label/ ← 保存裁剪后的標簽(.png)
├── split_vaihingen.py ← 運行腳本
pip install opencv-python tqdm numpy
split_vaihingen.py腳本源碼學習
import os
import cv2
import numpy as np
from glob import glob
from tqdm import tqdmtile_size = 512 # 裁剪窗口大小,512x512像素# 設置輸入路徑,分別是彩色原圖和灰度標簽圖目錄
image_dir = 'data/vaihingen/image'
label_dir = 'data/vaihingen/label'# 設置輸出路徑,分別保存裁剪后的圖像和標簽
out_img_dir = 'result/new_vaihingen/new_image'
out_lbl_dir = 'result/new_vaihingen/new_label'os.makedirs(out_img_dir, exist_ok=True) # 自動創建圖像輸出目錄
os.makedirs(out_lbl_dir, exist_ok=True) # 自動創建標簽輸出目錄# 獲取所有圖像和標簽路徑,并排序保證對應關系
image_paths = sorted(glob(os.path.join(image_dir, '*.tif')))
label_paths = sorted(glob(os.path.join(label_dir, '*_noBoundary.tif')))assert len(image_paths) == len(label_paths), "圖像和標簽數量不一致" # 確保一一對應# 遍歷所有圖像-標簽對
for img_path, lbl_path in tqdm(zip(image_paths, label_paths), total=len(image_paths), desc='正在處理'):image = cv2.imread(img_path, cv2.IMREAD_COLOR) # 讀取彩色圖if image is None:print(f"[錯誤] 無法讀取圖像文件: {img_path}")continuelabel = cv2.imread(lbl_path, cv2.IMREAD_GRAYSCALE) # 讀取灰度標簽if label is None:print(f"[錯誤] 無法讀取標簽文件: {lbl_path}")continueh, w = image.shape[:2] # 獲取圖像高寬# 從文件名中提取“area_數字”作為命名前綴basename = os.path.splitext(os.path.basename(img_path))[0] # 例如 top_mosaic_09cm_area1area_name = "area_" + basename.split("area")[-1] # 結果如 area_1# 按512像素步長遍歷圖像坐標進行裁剪for y in range(0, h, tile_size):for x in range(0, w, tile_size):tile_img = image[y:y+tile_size, x:x+tile_size] # 裁剪圖像塊tile_lbl = label[y:y+tile_size, x:x+tile_size] # 裁剪對應標簽塊# 如果標簽塊為空(無內容),跳過該塊if tile_lbl is None or tile_lbl.size == 0:print(f"[警告] tile_lbl 是空的,跳過該塊。坐標: ({x}, {y}) in {area_name}")continue# 判斷圖像塊是否足夠512*512,不夠則用黑色填充if tile_img.shape[0] < tile_size or tile_img.shape[1] < tile_size:pad_img = np.zeros((tile_size, tile_size, 3), dtype=np.uint8) # 黑色背景圖像pad_lbl = np.zeros((tile_size, tile_size), dtype=np.uint8) # 黑色背景標簽pad_img[:tile_img.shape[0], :tile_img.shape[1]] = tile_img # 復制原圖內容到左上角pad_lbl[:tile_lbl.shape[0], :tile_lbl.shape[1]] = tile_lbl # 復制標簽內容tile_img = pad_img # 替換為填充后的圖像tile_lbl = pad_lbl # 替換為填充后的標簽# 生成輸出文件名,含坐標信息確保唯一性img_name = f"{area_name}_{y}_{x}.jpg"lbl_name = f"{area_name}_{y}_{x}.png"# 保存裁剪好的圖像和標簽cv2.imwrite(os.path.join(out_img_dir, img_name), tile_img)cv2.imwrite(os.path.join(out_lbl_dir, lbl_name), tile_lbl)
詳細分步解析
import os
import cv2
import numpy as np
from glob import glob
from tqdm import tqdm
- 導入所需庫:
os
?處理路徑,cv2
?處理圖像,numpy
?做數組和填充,glob
?匹配文件,tqdm
?顯示進度條。
tile_size = 512
- 定義裁剪小塊的尺寸為512×512像素。
image_dir = 'data/vaihingen/image'
label_dir = 'data/vaihingen/label'
- 指定輸入路徑,分別是原始彩色大圖和對應的標簽灰度圖文件夾。
out_img_dir = 'result/new_vaihingen/new_image'
out_lbl_dir = 'result/new_vaihingen/new_label'
- 指定輸出路徑,用于保存裁剪后的小圖像和對應標簽。
os.makedirs(out_img_dir, exist_ok=True)
os.makedirs(out_lbl_dir, exist_ok=True)
- 自動創建輸出文件夾,如果已經存在則不會報錯。
image_paths = sorted(glob(os.path.join(image_dir, '*.tif')))
label_paths = sorted(glob(os.path.join(label_dir, '*_noBoundary.tif')))
- 利用
glob
?獲取所有符合后綴的文件路徑,并排序,確保圖像和標簽順序對應。
assert len(image_paths) == len(label_paths), "圖像和標簽數量不一致"
- 確保圖像和標簽數量匹配,一對一對應。
for img_path, lbl_path in tqdm(zip(image_paths, label_paths), total=len(image_paths), desc='正在處理'):
- 遍歷每對圖像和標簽路徑,用
tqdm
?顯示進度條。
image = cv2.imread(img_path, cv2.IMREAD_COLOR)if image is None:print(f"[錯誤] 無法讀取圖像文件: {img_path}")continue
- 讀取彩色圖像,如果讀取失敗則輸出錯誤并跳過。
label = cv2.imread(lbl_path, cv2.IMREAD_GRAYSCALE)if label is None:print(f"[錯誤] 無法讀取標簽文件: {lbl_path}")continue
- 讀取灰度標簽圖,失敗則報錯跳過。
h, w = image.shape[:2]
- 獲取當前圖像的高(行數)和寬(列數)。
basename = os.path.splitext(os.path.basename(img_path))[0]area_name = "area_" + basename.split("area")[-1]
- 解析圖像文件名,提取
area_數字
?作為命名前綴,方便區分圖像來源。
for y in range(0, h, tile_size):for x in range(0, w, tile_size):
- 以512步長在圖像寬高方向上滑動,遍歷每個裁剪窗口的左上角坐標。
tile_img = image[y:y+tile_size, x:x+tile_size]tile_lbl = label[y:y+tile_size, x:x+tile_size]
- 根據當前坐標裁剪圖像塊和對應標簽塊,大小通常是512×512,但邊緣可能小于512。
if tile_lbl is None or tile_lbl.size == 0:print(f"[警告] tile_lbl 是空的,跳過該塊。坐標: ({x}, {y}) in {area_name}")continue
- 如果標簽塊為空(可能是超出邊界或沒有內容),打印警告并跳過,避免無效數據。
if tile_img.shape[0] < tile_size or tile_img.shape[1] < tile_size:pad_img = np.zeros((tile_size, tile_size, 3), dtype=np.uint8)pad_lbl = np.zeros((tile_size, tile_size), dtype=np.uint8)pad_img[:tile_img.shape[0], :tile_img.shape[1]] = tile_imgpad_lbl[:tile_lbl.shape[0], :tile_lbl.shape[1]] = tile_lbltile_img = pad_imgtile_lbl = pad_lbl
-
判斷裁剪塊是否小于512×512(通常在圖像邊緣),如果是:
- 創建全黑的512×512空白圖像和標簽圖;
- 把原始裁剪塊數據復制到黑色背景的左上角;
- 這樣確保所有輸出塊尺寸統一為512×512。
img_name = f"{area_name}_{y}_{x}.jpg"lbl_name = f"{area_name}_{y}_{x}.png"
- 根據
area_數字
?和裁剪左上角坐標,生成唯一文件名,方便后續對應。
cv2.imwrite(os.path.join(out_img_dir, img_name), tile_img)cv2.imwrite(os.path.join(out_lbl_dir, lbl_name), tile_lbl)
- 將裁剪并(如需)填充后的圖像塊和標簽塊保存為文件,格式分別是jpg和png。
認識數據增強
數據增強(Data Augmentation)是對原始訓練數據進行各種變換操作,生成更多的“新”樣本,從而擴充訓練集。通過讓模型見到更多樣化的訓練樣本,減少過擬合,提高模型在真實環境中的表現。
常見的數據增強方法
幾何變換(Geometric Transformations)
- 旋轉(Rotation) :將圖像順時針或逆時針旋轉一定角度(如90度、180度、任意角度)。
- 翻轉(Flip) :水平翻轉(左右鏡像)或垂直翻轉(上下鏡像)。
- 平移(Translation) :圖像整體上下左右移動若干像素。
- 縮放(Scaling) :放大或縮小圖像尺寸。
- 裁剪(Crop) :隨機裁剪圖像的一部分,作為新樣本。
- 仿射變換(Affine Transform) :包括旋轉、縮放、平移和剪切等組合變換。
顏色變換(Color Transformations)
- 調整亮度(Brightness) :調節圖像整體亮度。
- 調整對比度(Contrast) :增強或減弱圖像對比度。
- 調整飽和度(Saturation) :改變圖像顏色的鮮艷度。
- 顏色抖動(Color Jitter) :隨機微調亮度、對比度、飽和度等。
添加噪聲(Noise Injection)
- 高斯噪聲(Gaussian Noise) :在像素值中加入高斯分布噪聲。
- 椒鹽噪聲(Salt-and-Pepper Noise) :隨機像素被替換為極端黑白值。
模糊和銳化(Blurring and Sharpening)
- 高斯模糊(Gaussian Blur) :圖像平滑處理,減弱細節。
- 銳化(Sharpening) :增強邊緣和細節。
復雜增強技術(Advanced Techniques)
- Cutout:隨機遮擋圖像的一部分區域,模擬遮擋。
- Mixup:將兩張圖像按一定比例混合,同時混合標簽。
- CutMix:剪切一塊圖像并粘貼到另一張圖像上,同時對應標簽也按比例調整。
數據增強的作用
- 增加訓練數據多樣性,降低過擬合風險。
- 提升模型的泛化能力,使其在各種變化環境下表現更穩健。
- 彌補樣本數量不足,尤其在數據采集困難或成本高時尤為重要。
Python中常用的數據增強庫
- Albumentations:功能豐富且高效的圖像增強庫。
- imgaug:支持復雜的序列增強操作。
- torchvision.transforms:PyTorch框架內置的常用增強函數。
- Keras ImageDataGenerator:Keras內置的實時圖像增強工具。