import tkinter as tk
from tkinter import filedialog
import cv2
import moviepy.editor as mp
import os
#第3回で追加#
import torch
from torchvision import models, transforms
#第3回で追加#
#第3回で追加#
# 事前学習済みのMask R-CNNモデルをロード
model = models.detection.maskrcnn_resnet50_fpn_v2(pretrained=True)
model.eval() # 評価モードにする
#第3回で追加#
# ファイルダイアログで動画を選択
def select_video_file():
root = tk.Tk()
root.withdraw() # Tkinterウィンドウを非表示にする
video_path = filedialog.askopenfilename(title="動画ファイルを選択", filetypes=[("動画ファイル", "*.mp4 *.avi *.mov *.mkv")])
return video_path
# 動画の音声と映像を出力
def process_video(input_video_path):
# 中間ファイル作成(映像だけ出力)
output_video_path_temp = input_video_path.rsplit('.', 1)[0] + "_outputtemp.mp4"
# 出力ファイルのパスを生成(元の動画と同じディレクトリにoutput.mp4として保存)
output_video_path = input_video_path.rsplit('.', 1)[0] + "_output.mp4"
# 動画キャプチャの準備
cap = cv2.VideoCapture(input_video_path)
original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
# 総フレーム数を取得
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 出力動画ファイルの作成(音声なしで映像のみ)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video_path_temp, fourcc, fps, (original_width, original_height))
frame_count = 0
while True:
# 動画の1フレームを読み込む
ret, frame = cap.read()
if not ret:
break # 動画の終わりに達したら終了
if 65 < frame_count < 215:
boxes , scores = process_torch(frame) #Fasterでframeから人を検知する
frame = draw_box(frame,boxes , scores)
#フレーム番号
frame = writeinfo(frame, frame_count, original_width, original_height)
# 目盛りを描画
frame = add_scale_to_frame(frame, original_width, original_height)
frame_count += 1 # フレーム番号をインクリメント
out.write(frame)
cv2.imshow("frame", frame)
cv2.waitKey(1)
# 動画キャプチャと書き込みを解放
cap.release()
out.release()
cv2.destroyAllWindows()
# 動画と音声をそのまま出力
add_audio_to_video(input_video_path, output_video_path_temp,output_video_path, total_frames,fps)
print(f"出力ファイル: {output_video_path}")
##第3回で追加##
def process_torch(frame):
input_image = preprocess_image(frame)
with torch.no_grad():
prediction = model(input_image)
boxes = prediction[0]['boxes'].cpu().numpy()
scores = prediction[0]['scores'].cpu().numpy()
return boxes,scores
def preprocess_image(image):
transform = transforms.Compose([
transforms.ToTensor(), # 画像をテンソルに変換
])
return transform(image).unsqueeze(0) # バッチ次元を追加
def draw_box(frame,boxes , scores):
# 信頼度が一定値以上の検出結果に四角形を描画
threshold = 0.9 # 信頼度の閾値(必要に応じて調整)
for i in range(len(boxes)):
if scores[i] > threshold:
x1, y1, x2, y2 = boxes[i]
# 四角形を描画 (色は青、太さは2)
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0), 2)
# scoreを丸める (小数第2位以下を切り捨て)
scores_M = int(scores[i] * 1000) / 1000
# 黒い文字でframe_countを表示
cv2.putText(frame, f"{scores_M}", (int(x1), int(y1)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
return frame
##第3回で追加##
##第2回で追加##
def writeinfo(frame,frame_count,width,height):
# フレームにframe_countを表示
# 白い背景の矩形を描画
cv2.rectangle(frame, (0, 0), (100, 40), (255, 255, 255), -1)
# 黒い文字でframe_countを表示
cv2.putText(frame, f"{frame_count}", (0, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
return frame
def add_scale_to_frame(frame, width, height, scale_interval=100, large_scale_interval=500):
# 左端と上端に目盛りを描画
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.5
font_thickness = 1
text_color = (0, 0, 0)
red_color = (0, 0, 255) # 赤色
green_color = (0, 255, 0) # 緑色
# 左端(縦の目盛り)
for y in range(scale_interval, height, scale_interval):
cv2.line(frame, (0, y), (10, y), (0, 0, 0), 2) # 目盛りの線
cv2.putText(frame, str(y), (15, y + 5), font, font_scale, text_color, font_thickness, cv2.LINE_AA) # 数値
# 上端(横の目盛り)
for x in range(scale_interval, width, scale_interval):
cv2.line(frame, (x, 0), (x, 10), (0, 0, 0), 2) # 目盛りの線
cv2.putText(frame, str(x), (x + 5, 20), font, font_scale, text_color, font_thickness, cv2.LINE_AA) # 数値
# 100ピクセルごとの交点に赤い点を描画
for x in range(scale_interval, width, scale_interval):
for y in range(scale_interval, height, scale_interval):
cv2.circle(frame, (x, y), 3, red_color, -1) # 赤い点
cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, red_color, font_thickness, cv2.LINE_AA) # 座標表示
# 500ピクセルごとの交点に緑の点を描画
for x in range(large_scale_interval, width, large_scale_interval):
for y in range(large_scale_interval, height, large_scale_interval):
cv2.circle(frame, (x, y), 3, green_color, -1) # 緑の点
cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, green_color, font_thickness, cv2.LINE_AA) # 座標表示
return frame
##第2回で追加##
# 音声を動画に統合する
def add_audio_to_video(input_video_path, output_video_path_temp,output_video_path, total_frames,fps):
#moviepyで動画と音声を読み込む
video_clip = mp.VideoFileClip(input_video_path)
audio_clip = video_clip.audio
# 映像部分の長さと音声の長さが一致するように、音声を調整
duration = total_frames / fps # 映像の長さ(秒)
audio_clip = audio_clip.subclip(0, duration) # 音声を映像の長さに合わせる
# 映像と音声を統合
final_video = mp.VideoFileClip(output_video_path_temp)
final_video = final_video.set_audio(audio_clip)
# 音声付きの動画を保存
final_video.write_videofile(output_video_path, codec="libx264", audio_codec="aac")
# 中間ファイル(output_video_path)を削除
if os.path.exists(output_video_path_temp):
os.remove(output_video_path_temp)
print(f"中間ファイル {output_video_path_temp} を削除しました。")
print(f"音声付きの動画が保存されました。最終ファイル名: {output_video_path_temp}")
# メイン処理
def main():
video_file = select_video_file()
if video_file:
process_video(video_file)
else:
print("動画ファイルが選択されませんでした。")
if __name__ == "__main__":
main()