diff options
Diffstat (limited to 'utils/rkisp1')
-rwxr-xr-x | utils/rkisp1/gen-csc-table.py | 215 | ||||
-rwxr-xr-x | utils/rkisp1/rkisp1-capture.sh | 64 |
2 files changed, 274 insertions, 5 deletions
diff --git a/utils/rkisp1/gen-csc-table.py b/utils/rkisp1/gen-csc-table.py new file mode 100755 index 00000000..ffc0370a --- /dev/null +++ b/utils/rkisp1/gen-csc-table.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2022, Ideas on Board Oy +# +# Generate color space conversion table coefficients with configurable +# fixed-point precision + +import argparse +import enum +import numpy as np +import sys + + +encodings = { + 'rec601': [ + [ 0.299, 0.587, 0.114 ], + [ -0.299 / 1.772, -0.587 / 1.772, 0.886 / 1.772 ], + [ 0.701 / 1.402, -0.587 / 1.402, -0.114 / 1.402 ] + ], + 'rec709': [ + [ 0.2126, 0.7152, 0.0722 ], + [ -0.2126 / 1.8556, -0.7152 / 1.8556, 0.9278 / 1.8556 ], + [ 0.7874 / 1.5748, -0.7152 / 1.5748, -0.0722 / 1.5748 ] + ], + 'rec2020': [ + [ 0.2627, 0.6780, 0.0593 ], + [ -0.2627 / 1.8814, -0.6780 / 1.8814, 0.9407 / 1.8814 ], + [ 0.7373 / 1.4746, -0.6780 / 1.4746, -0.0593 / 1.4746 ], + ], + 'smpte240m': [ + [ 0.2122, 0.7013, 0.0865 ], + [ -0.2122 / 1.8270, -0.7013 / 1.8270, 0.9135 / 1.8270 ], + [ 0.7878 / 1.5756, -0.7013 / 1.5756, -0.0865 / 1.5756 ], + ], +} + + +class Precision(object): + def __init__(self, precision): + if precision[0].upper() != 'Q': + raise RuntimeError(f'Invalid precision `{precision}`') + prec = precision[1:].split('.') + if len(prec) != 2: + raise RuntimeError(f'Invalid precision `{precision}`') + + self.__prec = [int(v) for v in prec] + + @property + def integer(self): + return self.__prec[0] + + @property + def fractional(self): + return self.__prec[1] + + @property + def total(self): + # Add 1 for the sign bit + return self.__prec[0] + self.__prec[1] + 1 + + +class Quantization(enum.Enum): + FULL = 0 + LIMITED = 1 + + +def scale_coeff(coeff, quantization, luma): + """Scale a coefficient to the output range dictated by the quantization. + + Parameters + ---------- + coeff : float + The CSC matrix coefficient to scale + quantization : Quantization + The quantization, either FULL or LIMITED + luma : bool + True if the coefficient corresponds to a luma value, False otherwise + """ + + # Assume the input range is 8 bits. The output range is set by the + # quantization and differs between luma and chrome components for limited + # range. + in_range = 255 - 0 + if quantization == Quantization.FULL: + out_range = 255 - 0 + elif luma: + out_range = 235 - 16 + else: + out_range = 240 - 16 + + return coeff * out_range / in_range + + +def round_array(values): + """Round a list of signed floating point values to the closest integer while + preserving the (rounded) value of the sum of all elements. + """ + + # Calculate the rounding error as the difference between the rounded sum of + # values and the sum of rounded values. This is by definition an integer + # (positive or negative), which indicates how many values will need to be + # 'flipped' to the opposite rounding. + rounded_values = [round(value) for value in values] + sum_values = round(sum(values)) + sum_error = sum_values - sum(rounded_values) + + if sum_error == 0: + return rounded_values + + # The next step is to distribute the error among the values, in a way that + # will minimize the relative error introduced in individual values. We + # extend the values list with the rounded value and original index for each + # element, and sort by rounding error. Then we modify the elements with the + # highest or lowest error, depending on whether the sum error is negative + # or positive. + + values = [[value, round(value), index] for index, value in enumerate(values)] + values.sort(key=lambda v: v[1] - v[0]) + + # It could also be argued that the key for the sort order should not be the + # absolute rouding error but the relative error, as the impact of identical + # rounding errors will differ for coefficients with widely different values. + # This is a topic for further research. + # + # values.sort(key=lambda v: (v[1] - v[0]) / abs(v[0])) + + if sum_error > 0: + for i in range(sum_error): + values[i][1] += 1 + else: + for i in range(-sum_error): + values[len(values) - i - 1][1] -= 1 + + # Finally, sort back by index, make sure the total rounding error is now 0, + # and return the rounded values. + values.sort(key=lambda v: v[2]) + values = [value[1] for value in values] + assert(sum(values) == sum_values) + + return values + + +def main(argv): + + # Parse command line arguments. + parser = argparse.ArgumentParser( + description='Generate color space conversion table coefficients with ' + 'configurable fixed-point precision.' + ) + parser.add_argument('--invert', '-i', action='store_true', + help='Invert the color space conversion (YUV -> RGB)') + parser.add_argument('--precision', '-p', default='Q1.7', + help='The output fixed point precision in Q notation (sign bit excluded)') + parser.add_argument('--quantization', '-q', choices=['full', 'limited'], + default='limited', help='Quantization range') + parser.add_argument('encoding', choices=encodings.keys(), help='YCbCr encoding') + args = parser.parse_args(argv[1:]) + + try: + precision = Precision(args.precision) + except Exception: + print(f'Invalid precision `{args.precision}`') + return 1 + + encoding = encodings[args.encoding] + quantization = Quantization[args.quantization.upper()] + + # Scale and round the encoding coefficients based on the precision and + # quantization range. + luma = True + scaled_coeffs = [] + for line in encoding: + line = [scale_coeff(coeff, quantization, luma) for coeff in line] + scaled_coeffs.append(line) + luma = False + + if args.invert: + scaled_coeffs = np.linalg.inv(scaled_coeffs) + + rounded_coeffs = [] + for line in scaled_coeffs: + line = [coeff * (1 << precision.fractional) for coeff in line] + # For the RGB to YUV conversion, use a rounding method that preserves + # the rounded sum of each line to avoid biases and overflow, as the sum + # of luma and chroma coefficients should be 1.0 and 0.0 respectively + # (in full range). For the YUV to RGB conversion, there is no such + # constraint, so use simple rounding. + if args.invert: + line = [round(coeff) for coeff in line] + else: + line = round_array(line) + + # Convert coefficients to the number of bits selected by the precision. + # Negative values will be turned into positive integers using 2's + # complement. + line = [coeff & ((1 << precision.total) - 1) for coeff in line] + rounded_coeffs.append(line) + + # Print the result as C code. + nbits = 1 << (precision.total - 1).bit_length() + nbytes = nbits // 4 + print(f'static const u{nbits} {"yuv2rgb" if args.invert else "rgb2yuv"}_{args.encoding}_{quantization.name.lower()}_coeffs[] = {{') + + for line in rounded_coeffs: + line = [f'0x{coeff:0{nbytes}x}' for coeff in line] + + print(f'\t{", ".join(line)},') + + print('};') + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/rkisp1/rkisp1-capture.sh b/utils/rkisp1/rkisp1-capture.sh index 4d09f5d5..d767e31d 100755 --- a/utils/rkisp1/rkisp1-capture.sh +++ b/utils/rkisp1/rkisp1-capture.sh @@ -4,8 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# rkisp-capture.sh - Capture processed frames from cameras based on the -# Rockchip ISP1 +# Capture processed frames from cameras based on the Rockchip ISP1 # # The scripts makes use of the following tools, which are expected to be # executable from the system-wide path or from the local directory: @@ -14,6 +13,37 @@ # - raw2rgbpnm (from git://git.retiisi.org.uk/~sailus/raw2rgbpnm.git) # - yavta (from git://git.ideasonboard.org/yavta.git) +# Return the entity connected to a given pad +# $1: The pad, expressed as "entity":index +mc_remote_entity() { + local entity="${1%:*}" + local pad="${1#*:}" + + ${mediactl} -p | awk ' +/^- entity / { + in_entity=0 +} + +/^- entity [0-9]+: '"${entity}"' / { + in_entity=1 +} + +/^[ \t]+pad/ { + in_pad=0 +} + +/^[ \t]+pad'"${pad}"': / { + in_pad=1 +} + +/^[ \t]+(<-|->) "[^"]+"/ { + if (in_entity && in_pad) { + print gensub(/^[^"]+"([^"]+)":([0-9]+).*$/, "\\1", "g") + exit + } +}' +} + # Locate the sensor entity find_sensor() { local bus @@ -28,6 +58,17 @@ find_sensor() { echo "$sensor_name $bus" } +# Locate the CSI-2 receiver +find_csi2_rx() { + local sensor_name=$1 + local csi2_rx + + csi2_rx=$(mc_remote_entity "$sensor_name:0") + if [ "$csi2_rx" != rkisp1_isp ] ; then + echo "$csi2_rx" + fi +} + # Locate the media device find_media_device() { local mdev @@ -51,7 +92,7 @@ get_sensor_format() { local format local sensor=$1 - format=$($mediactl --get-v4l2 "'$sensor':0" | sed 's/\[\([^ ]*\).*/\1/') + format=$($mediactl --get-v4l2 "'$sensor':0" | grep 'fmt:' | sed 's/.*\(fmt:\S*\).*/\1/') sensor_mbus_code=$(echo $format | sed 's/fmt:\([A-Z0-9_]*\).*/\1/') sensor_size=$(echo $format | sed 's/[^\/]*\/\([0-9x]*\).*/\1/') @@ -63,15 +104,27 @@ configure_pipeline() { local format="fmt:$sensor_mbus_code/$sensor_size" local capture_mbus_code=$1 local capture_size=$2 + local csi2_rx echo "Configuring pipeline for $sensor in $format" + csi2_rx=$(find_csi2_rx "$sensor") + $mediactl -r - $mediactl -l "'$sensor':0 -> 'rkisp1_isp':0 [1]" + if [ -n "$csi2_rx" ] ; then + $mediactl -l "'$sensor':0 -> '$csi2_rx':0 [1]" + $mediactl -l "'$csi2_rx':1 -> 'rkisp1_isp':0 [1]" + else + $mediactl -l "'$sensor':0 -> 'rkisp1_isp':0 [1]" + fi $mediactl -l "'rkisp1_isp':2 -> 'rkisp1_resizer_mainpath':0 [1]" $mediactl -V "\"$sensor\":0 [$format]" + if [ -n "$csi2_rx" ] ; then + $mediactl -V "'$csi2_rx':0 [$format]" + $mediactl -V "'$csi2_rx':1 [$format]" + fi $mediactl -V "'rkisp1_isp':0 [$format crop:(0,0)/$sensor_size]" $mediactl -V "'rkisp1_isp':2 [fmt:$capture_mbus_code/$sensor_size crop:(0,0)/$sensor_size]" $mediactl -V "'rkisp1_resizer_mainpath':0 [fmt:$capture_mbus_code/$sensor_size crop:(0,0)/$sensor_size]" @@ -88,6 +141,7 @@ capture_frames() { if [[ $save_file -eq 1 ]]; then file_op="--file=/tmp/frame-#.bin" + rm -f /tmp/frame-*.bin fi yavta -c$frame_count -n5 -I -f $capture_format -s $capture_size \ @@ -170,7 +224,7 @@ mediactl="media-ctl -d $mdev" get_sensor_format "$sensor" if [[ $raw == true ]] ; then - capture_format=$(echo $sensor_mbus_code | sed 's/_[0-9X]$//') + capture_format=$(echo $sensor_mbus_code | sed 's/_[0-9X]*$//') capture_mbus_code=$sensor_mbus_code else capture_format=YUYV |