def weighted_search(num1, vmaf1, num2, vmaf2, target): """ Returns weighted value closest to searched :param num1: Q of first probe :param vmaf1: VMAF of first probe :param num2: Q of second probe :param vmaf2: VMAF of first probe :param target: VMAF target :return: Q for new probe """ dif1 = abs(VMAF.transform_vmaf(target) - VMAF.transform_vmaf(vmaf2)) dif2 = abs(VMAF.transform_vmaf(target) - VMAF.transform_vmaf(vmaf1)) tot = dif1 + dif2 new_point = int(round(num1 * (dif1 / tot) + (num2 * (dif2 / tot)))) return new_point
def fast_search(self, chunk): """ Experimental search Use Euler's method with known relation between cq and vmaf Formula -ln(1-score/100) = vmaf_cq_deriv*last_q + constant constant = -ln(1-score/100) - vmaf_cq_deriv*last_q Formula -ln(1-project.vmaf_target/100) = vmaf_cq_deriv*cq + constant cq = (-ln(1-project.vmaf_target/100) - constant)/vmaf_cq_deriv """ vmaf_cq = [] q_list = [] # Make middle probe middle_point = (self.min_q + self.max_q) // 2 q_list.append(middle_point) last_q = middle_point score = VMAF.read_weighted_vmaf(self.vmaf_probe(chunk, last_q)) vmaf_cq.append((score, last_q)) vmaf_cq_deriv = -0.18 next_q = int( round(last_q + (VMAF.transform_vmaf(self.target) - VMAF.transform_vmaf(score)) / vmaf_cq_deriv)) # Clamp if next_q < self.min_q: next_q = self.min_q if self.max_q < next_q: next_q = self.max_q # Single probe cq guess or exit to avoid divide by zero if self.probes == 1 or next_q == last_q: self.log_probes(vmaf_cq, chunk.frames, chunk.name, next_q, self.target) return next_q # Second probe at guessed value score_2 = VMAF.read_weighted_vmaf(self.vmaf_probe(chunk, next_q)) # Calculate slope vmaf_cq_deriv = (VMAF.transform_vmaf(score_2) - VMAF.transform_vmaf(score)) / (next_q - last_q) # Same deal different slope next_q = int( round(next_q + (VMAF.transform_vmaf(self.target) - VMAF.transform_vmaf(score_2)) / vmaf_cq_deriv)) # Clamp if next_q < self.min_q: next_q = self.min_q if self.max_q < next_q: next_q = self.max_q self.log_probes(vmaf_cq, chunk.frames, chunk.name, next_q, self.target) return next_q
def per_shot_target_quality(chunk: Chunk, project: Project): vmaf_cq = [] frames = chunk.frames # get_scene_scores(chunk, project.ffmpeg_pipe) # Adapt probing rate if project.probing_rate in (1, 2): probing_rate = project.probing_rate else: probing_rate = adapt_probing_rate(project.probing_rate, frames) q_list = [] score = 0 # Make middle probe middle_point = (project.min_q + project.max_q) // 2 q_list.append(middle_point) last_q = middle_point score = VMAF.read_weighted_vmaf( vmaf_probe(chunk, last_q, project, probing_rate)) vmaf_cq.append((score, last_q)) if project.probes < 3: #Use Euler's method with known relation between cq and vmaf vmaf_cq_deriv = -0.18 ## Formula -ln(1-score/100) = vmaf_cq_deriv*last_q + constant #constant = -ln(1-score/100) - vmaf_cq_deriv*last_q ## Formula -ln(1-project.vmaf_target/100) = vmaf_cq_deriv*cq + constant #cq = (-ln(1-project.vmaf_target/100) - constant)/vmaf_cq_deriv next_q = int( round(last_q + (VMAF.transform_vmaf(project.target_quality) - VMAF.transform_vmaf(score)) / vmaf_cq_deriv)) #Clamp if next_q < project.min_q: next_q = project.min_q if project.max_q < next_q: next_q = project.max_q #Single probe cq guess or exit to avoid divide by zero if project.probes == 1 or next_q == last_q: return next_q #Second probe at guessed value score_2 = VMAF.read_weighted_vmaf( vmaf_probe(chunk, next_q, project, probing_rate)) #Calculate slope vmaf_cq_deriv = (VMAF.transform_vmaf(score_2) - VMAF.transform_vmaf(score)) / (next_q - last_q) #Same deal different slope next_q = int( round(next_q + (VMAF.transform_vmaf(project.target_quality) - VMAF.transform_vmaf(score_2)) / vmaf_cq_deriv)) #Clamp if next_q < project.min_q: next_q = project.min_q if project.max_q < next_q: next_q = project.max_q return next_q # Initialize search boundary vmaf_lower = score vmaf_upper = score vmaf_cq_lower = last_q vmaf_cq_upper = last_q # Branch if score < project.target_quality: next_q = project.min_q q_list.append(project.min_q) else: next_q = project.max_q q_list.append(project.max_q) # Edge case check score = VMAF.read_weighted_vmaf( vmaf_probe(chunk, next_q, project, probing_rate)) vmaf_cq.append((score, next_q)) if next_q == project.min_q and score < project.target_quality: log(f"Chunk: {chunk.name}, Rate: {probing_rate}, Fr: {frames}\n" f"Q: {sorted([x[1] for x in vmaf_cq])}, Early Skip Low CQ\n" f"Vmaf: {sorted([x[0] for x in vmaf_cq], reverse=True)}\n" f"Target Q: {vmaf_cq[-1][1]} VMAF: {round(vmaf_cq[-1][0], 2)}\n\n") return next_q elif next_q == project.max_q and score > project.target_quality: log(f"Chunk: {chunk.name}, Rate: {probing_rate}, Fr: {frames}\n" f"Q: {sorted([x[1] for x in vmaf_cq])}, Early Skip High CQ\n" f"Vmaf: {sorted([x[0] for x in vmaf_cq], reverse=True)}\n" f"Target Q: {vmaf_cq[-1][1]} VMAF: {round(vmaf_cq[-1][0], 2)}\n\n") return next_q # Set boundary if score < project.target_quality: vmaf_lower = score vmaf_cq_lower = next_q else: vmaf_upper = score vmaf_cq_upper = next_q # VMAF search for _ in range(project.probes - 2): new_point = weighted_search(vmaf_cq_lower, vmaf_lower, vmaf_cq_upper, vmaf_upper, project.target_quality) if new_point in [x[1] for x in vmaf_cq]: break q_list.append(new_point) score = VMAF.read_weighted_vmaf( vmaf_probe(chunk, new_point, project, probing_rate)) vmaf_cq.append((score, new_point)) # Update boundary if score < project.target_quality: vmaf_lower = score vmaf_cq_lower = new_point else: vmaf_upper = score vmaf_cq_upper = new_point q, q_vmaf = get_target_q(vmaf_cq, project.target_quality) log(f'Chunk: {chunk.name}, Rate: {probing_rate}, Fr: {frames}\n' f'Q: {sorted([x[1] for x in vmaf_cq])}\n' f'Vmaf: {sorted([x[0] for x in vmaf_cq], reverse=True)}\n' f'Target Q: {q} VMAF: {round(q_vmaf, 2)}\n\n') # Plot Probes if project.vmaf_plots and len(vmaf_cq) > 3: plot_probes(project, vmaf_cq, chunk, frames) return q