Ovaj Python GUI program omogućava korisnicima da poboljšaju kvalitet videa pomoću detaljnih parametara obrade slike. Namenjen je svima koji žele da optimizuju oštrinu, kontrast, svetlinu, zasićenost i ekspoziciju video materijala na jednostavan i vizuelno intuitivan način. Program je pre svega namenjen starim video snimcima digitalizovanim sa traka do formata 640x480p.
Glavne mogućnosti:
- Učitavanje video fajlova: podržava
.mp4,.avii.mov. - Vizuelni prikaz: originalni i obrađeni frejm prikazani su jedan pored drugog.
- Podesivi parametri:
- Oštrina (sharpen)
- Kontrast
- Svetlina (brightness)
- Zasićenost (saturation)
- Izložnost / gama korekcija
- Skaliranje rezolucije (u procentima)
- Biranje određenog frejma za prikaz
- Export celog videa: nakon podešavanja željenih parametara, kompletan video se može izvesti u novom MP4 fajlu.
Kako se koristi:
- Pokrenite program:
python3 video_app.py - Kliknite na “Učitaj video” i izaberite željeni fajl.
- Podesite željene parametre pomoću klizača.
- Pregledajte rezultat u realnom vremenu (za izabrani frejm).
- Kada ste zadovoljni, kliknite “Exportuj ceo video” i sačuvajte rezultat.
Program koristi biblioteke: OpenCV, Pillow, Tkinter, NumPy.
Evo jednostavnog Bash skripta koji instalira sve potrebne zavisnosti za pokretanje ovog programa na Linuxu (Debian/Ubuntu sistem):
#instalacija_porebnih_zavisnosti.sh #!/bin/bash echo "Instalacija zavisnosti za VideoSharpenApp..." # Ažuriranje liste paketa sudo apt update # Instalacija sistemskih biblioteka sudo apt install -y python3 python3-pip python3-tk python3-opencv libgl1-mesa-glx # Instalacija Python biblioteka pip3 install --upgrade pip pip3 install numpy opencv-python pillow echo "Instalacija završena."
Programski kod za videobolji.py:
#videobolji.py
# The MIT License (MIT)
# Copyright (c) 2025 Aleksandar Maričić
#
# Ovim se omogućava bilo kome da koristi, kopira, menja, spaja, objavljuje,
# distribuira, daje podlicencu i/ili prodaje kopije ovog softverskog programa,
# uz uslov da u svim kopijama ili značajnim delovima softverskog programa bude
# uključena sledeća obavest:
#
# Copyright (c) 2025 Aleksandar Maričić
#
# Ovaj softverski program je pružen "takav kakav jeste", bez bilo kakvih garancija,
# izričitih ili impliciranih, uključujući, ali ne ograničavajući se na, garancije o
# prikladnosti za prodaju ili pogodnosti za određenu svrhu. U svakom slučaju, autori
# ili nosioci prava nisu odgovorni za bilo kakvu štetu ili druge obaveze koje mogu nastati
# usled upotrebe ovog softverskog programa.
# Naziv programa: Program za poboljšanje kvaliteta video snimaka
# instalacija potrebnih zavisnosti: python3 -m pip install opencv-python numpy Pillow
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
from PIL import Image, ImageTk, ImageEnhance
def sharpen_image(image, strength):
kernel_value = strength
kernel = np.array([
[0, -1, 0],
[-1, kernel_value, -1],
[0, -1, 0]
])
return cv2.filter2D(image, -1, kernel)
def adjust_contrast(image, factor):
enhancer = ImageEnhance.Contrast(image)
return enhancer.enhance(factor)
def adjust_brightness(image, factor):
enhancer = ImageEnhance.Brightness(image)
return enhancer.enhance(factor)
def adjust_saturation(image, factor):
enhancer = ImageEnhance.Color(image)
return enhancer.enhance(factor)
def adjust_exposure(image, gamma):
inv_gamma = 1.0 / gamma
table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in np.arange(256)]).astype("uint8")
return cv2.LUT(image, table)
class VideoSharpenApp:
def __init__(self, root):
self.root = root
self.root.title("Video poboljšanje slike - detaljni parametri")
# Frame za prikaz slika
self.image_frame = tk.Frame(root)
self.image_frame.pack(pady=5)
self.original_panel = tk.Label(self.image_frame, text="Original")
self.original_panel.pack(side="left", padx=10, pady=5)
self.processed_panel = tk.Label(self.image_frame, text="Obrađeno")
self.processed_panel.pack(side="right", padx=10, pady=5)
# Scrollable frame za kontrole
control_container = tk.Frame(root)
control_container.pack(fill='both', expand=True)
canvas = tk.Canvas(control_container)
canvas.pack(side='left', fill='both', expand=True)
scrollbar = ttk.Scrollbar(control_container, orient='vertical', command=canvas.yview)
scrollbar.pack(side='right', fill='y')
canvas.configure(yscrollcommand=scrollbar.set)
self.controls_frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=self.controls_frame, anchor='nw')
def on_frame_configure(event):
canvas.configure(scrollregion=canvas.bbox("all"))
self.controls_frame.bind("<Configure>", on_frame_configure)
# Klizači i dugmad (raspoređeni u mrežu 3 kolone)
# Kreiramo listu sa parametrima da ih lakše rasporedimo
sliders_info = [
("Oštrina (1.00 - 15.00)", 100, 1500, 500),
("Kontrast (0.5 - 3.0)", 50, 300, 100),
("Svetlina (0.5 - 3.0)", 50, 300, 100),
("Zasićenost (0.0 - 3.0)", 0, 300, 100),
("Izložnost/Gama (0.1 - 3.0)", 10, 300, 100),
("Frejm", 0, 100, 0),
("Skaliranje rezolucije (%)", 50, 300, 100)
]
self.sliders = []
# Napravi grid 3 kolone
for i, (label, frm, to, default) in enumerate(sliders_info):
scale = tk.Scale(self.controls_frame,
from_=frm,
to=to,
orient=tk.HORIZONTAL,
label=label,
resolution=1 if label != "Frejm" else 1,
command=self.update_frame_preview)
scale.set(default)
row = i // 3
col = i % 3
scale.grid(row=row, column=col, sticky='ew', padx=5, pady=5)
self.controls_frame.grid_columnconfigure(col, weight=1)
self.sliders.append(scale)
# Posebno dugmad ispod klizača
btn_frame = tk.Frame(self.controls_frame)
btn_frame.grid(row=(len(sliders_info) + 2) // 3, column=0, columnspan=3, pady=10, sticky='ew')
self.load_button = tk.Button(btn_frame, text="Učitaj video", command=self.load_video)
self.load_button.pack(side='left', expand=True, fill='x', padx=5)
self.export_button = tk.Button(btn_frame, text="Exportuj ceo video", command=self.export_video)
self.export_button.pack(side='left', expand=True, fill='x', padx=5)
# Interni podaci
self.video_path = ""
self.cap = None
self.total_frames = 0
self.fps = 25
self.frame_width = 0
self.frame_height = 0
def load_video(self):
path = filedialog.askopenfilename(filetypes=[("Video fajlovi", "*.mp4 *.avi *.mov")])
if not path:
return
self.video_path = path
self.cap = cv2.VideoCapture(path)
if not self.cap.isOpened():
messagebox.showerror("Greška", "Ne mogu da otvorim video.")
return
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.sliders[5].configure(to=max(0, self.total_frames - 1)) # Frejm slider
self.sliders[5].set(0)
self.update_frame_preview()
# Podesi veličinu prozora da stane dve slike + kontrole
width = self.frame_width * 2 + 60
height = self.frame_height + 220
self.root.geometry(f"{width}x{height}")
def get_frame_at(self, index):
if not self.cap:
return None
self.cap.set(cv2.CAP_PROP_POS_FRAMES, index)
ret, frame = self.cap.read()
if not ret:
return None
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
def update_frame_preview(self, event=None):
if not self.cap:
return
frame_idx = self.sliders[5].get()
sharpness = self.sliders[0].get() / 100.0
contrast = self.sliders[1].get() / 100.0
brightness = self.sliders[2].get() / 100.0
saturation = self.sliders[3].get() / 100.0
exposure = self.sliders[4].get() / 100.0
scale_percent = self.sliders[6].get()
frame = self.get_frame_at(frame_idx)
if frame is None:
return
# Prikaz originalnog frejma
orig_img = Image.fromarray(frame)
orig_photo = ImageTk.PhotoImage(orig_img)
self.original_panel.configure(image=orig_photo)
self.original_panel.image = orig_photo
# Obrada slike
processed = sharpen_image(frame, sharpness)
processed = adjust_exposure(processed, exposure)
pil_img = Image.fromarray(processed)
pil_img = adjust_contrast(pil_img, contrast)
pil_img = adjust_brightness(pil_img, brightness)
pil_img = adjust_saturation(pil_img, saturation)
# Skaliranje
new_w = int(pil_img.width * scale_percent / 100)
new_h = int(pil_img.height * scale_percent / 100)
pil_img = pil_img.resize((new_w, new_h), Image.LANCZOS)
proc_photo = ImageTk.PhotoImage(pil_img)
self.processed_panel.configure(image=proc_photo)
self.processed_panel.image = proc_photo
def export_video(self):
if not self.cap:
messagebox.showerror("Greška", "Video nije učitan.")
return
sharpness = self.sliders[0].get() / 100.0
contrast = self.sliders[1].get() / 100.0
brightness = self.sliders[2].get() / 100.0
saturation = self.sliders[3].get() / 100.0
exposure = self.sliders[4].get() / 100.0
scale_percent = self.sliders[6].get()
output_path = filedialog.asksaveasfilename(defaultextension=".mp4", filetypes=[("MP4 fajl", "*.mp4")])
if not output_path:
return
new_w = int(self.frame_width * scale_percent / 100)
new_h = int(self.frame_height * scale_percent / 100)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
total = int(self.total_frames)
out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, (new_w, new_h))
print(f"Exportujem video u {output_path}...")
for i in range(total):
ret, frame = self.cap.read()
if not ret:
break
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
processed = sharpen_image(rgb_frame, sharpness)
processed = adjust_exposure(processed, exposure)
pil_img = Image.fromarray(processed)
pil_img = adjust_contrast(pil_img, contrast)
pil_img = adjust_brightness(pil_img, brightness)
pil_img = adjust_saturation(pil_img, saturation)
new_frame = pil_img.resize((new_w, new_h), Image.LANCZOS)
new_frame_cv = cv2.cvtColor(np.array(new_frame), cv2.COLOR_RGB2BGR)
out.write(new_frame_cv)
if i % 30 == 0:
print(f"{i}/{total} frejmova obrađeno...")
out.release()
messagebox.showinfo("Završeno", f"Video sačuvan:\n{output_path}")
if __name__ == "__main__":
root = tk.Tk()
app = VideoSharpenApp(root)
root.mainloop()
