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
,.avi
i.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()