Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minimum tested pixel count to rotate the document #52

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CmdLineOptions.pas
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface
const
DefaultThreshold = 128;
DefaultMaxAngle = 10;
DefaultMinTestedPixels = 25;
DefaultSkipAngle = 0.01;

type
Expand Down Expand Up @@ -54,6 +55,7 @@ TCmdLineOptions = class
FInputFileName: string;
FOutputFileName: string;
FMaxAngle: Double;
FMinTestedPixels: Integer;
FSkipAngle: Double;
FResamplingFilter: TResamplingFilter;
FThresholdingMethod: TThresholdingMethod;
Expand Down Expand Up @@ -86,6 +88,8 @@ TCmdLineOptions = class
property OutputFileName: string read FOutputFileName;
// Max expected rotation angle - algo then works in range [-MaxAngle, MaxAngle]
property MaxAngle: Double read FMaxAngle;
// Minimum tested pixels for any rotation
property MinTestedPixels: Integer read FMinTestedPixels;
// Skew threshold angle - skip deskewing if detected skew angle is in range (-MinAngle, MinAngle)
property SkipAngle: Double read FSkipAngle;
// Resampling filter used for rotations
Expand Down Expand Up @@ -155,6 +159,7 @@ constructor TCmdLineOptions.Create;
begin
FThresholdLevel := DefaultThreshold;
FMaxAngle := DefaultMaxAngle;
FMinTestedPixels := DefaultMinTestedPixels;
FSkipAngle := DefaultSkipAngle;
FResamplingFilter := rfLinear;
FThresholdingMethod := tmOtsu;
Expand All @@ -174,6 +179,7 @@ constructor TCmdLineOptions.Create;
function TCmdLineOptions.GetIsValid: Boolean;
begin
Result := (InputFileName <> '') and (MaxAngle > 0) and (SkipAngle >= 0) and
(MinTestedPixels > 0) and
((ThresholdingMethod in [tmOtsu]) or (ThresholdingMethod = tmExplicit) and (ThresholdLevel > 0));
end;

Expand Down Expand Up @@ -292,6 +298,11 @@ function TCmdLineOptions.ParseCommnadLine: Boolean;
if not TryStrToFloat(Value, FMaxAngle, FFormatSettings) then
FErrorMessage := 'Invalid value for max angle parameter: ' + Value;
end
else if Param = '-m' then
begin
if not TryStrToInt(Value, FMinTestedPixels) then
FErrorMessage := 'Invalid value for minimum tested pixels parameter: ' + Value;
end
else if Param = '-l' then
begin
if not TryStrToFloat(Value, FSkipAngle, FFormatSettings) then
Expand Down Expand Up @@ -556,6 +567,7 @@ function TCmdLineOptions.OptionsToString: string;
' input file = ' + InputFileName + sLineBreak +
' output file = ' + OutputFileName + sLineBreak +
' max angle = ' + FloatToStr(MaxAngle) + sLineBreak +
' min tested pixels = ' + FloatToStr(MinTestedPixels) + sLineBreak +
' background color = ' + IntToHex(BackgroundColor, 8) + sLineBreak +
' resampling filter = ' + FilterStr + sLineBreak +
' thresholding method = ' + Iff(ThresholdingMethod = tmExplicit, 'explicit', 'auto otsu') + sLineBreak +
Expand Down
5 changes: 3 additions & 2 deletions MainUnit.pas
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ procedure WriteUsage;
WriteLn(' Options:');
WriteLn(' -o output: Output image file name (default: prefixed input as png)');
WriteLn(' -a angle: Maximal expected skew angle (both directions) in degrees (default: 10)');
WriteLn(' -m min_tested: Minimum number of tested pixels for any rotation (default: 25)');
WriteLn(' -b color: Background color in hex format RRGGBB|LL|AARRGGBB (default: black)');
WriteLn(' Ext. options:');
WriteLn(' -q filter: Resampling filter used for rotations (default: linear');
Expand Down Expand Up @@ -199,8 +200,8 @@ function DoDeskew: Boolean;
// Main step - calculate image rotation SkewAngle
WriteLn('Calculating skew angle...');
Time := GetTimeMicroseconds;
SkewAngle := CalcRotationAngle(Options.MaxAngle, Threshold,
InputImage.Width, InputImage.Height, InputImage.Bits,
SkewAngle := CalcRotationAngle(Options.MaxAngle, Options.MinTestedPixels,
Threshold, InputImage.Width, InputImage.Height, InputImage.Bits,
@ContentRect, @Stats);
WriteTiming('Skew detection');
WriteLn('Skew angle found [deg]: ', SkewAngle:4:3);
Expand Down
17 changes: 11 additions & 6 deletions RotationDetector.pas
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ TCalcSkewAngleStats = record
work only in defined part of image (useful when the document has text only in
smaller area of page and non-text features outside the area confuse the rotation detector).
Various calculations stats can be retrieved by passing Stats parameter.}
function CalcRotationAngle(const MaxAngle: Double; Treshold: Integer;
Width, Height: Integer; Pixels: PByteArray; DetectionArea: PRect = nil;
function CalcRotationAngle(const MaxAngle: Double; const MinTestedPixels: Integer;
Treshold: Integer; Width, Height: Integer; Pixels: PByteArray; DetectionArea: PRect = nil;
Stats: PCalcSkewAngleStats = nil): Double;

implementation

function CalcRotationAngle(const MaxAngle: Double; Treshold: Integer;
Width, Height: Integer; Pixels: PByteArray; DetectionArea: PRect; Stats: PCalcSkewAngleStats): Double;
function CalcRotationAngle(const MaxAngle: Double; const MinTestedPixels: Integer;
Treshold: Integer; Width, Height: Integer; Pixels: PByteArray; DetectionArea:
PRect; Stats: PCalcSkewAngleStats): Double;
const
// Number of "best" lines we take into account when determining
// resulting rotation angle (lines with most votes).
Expand Down Expand Up @@ -193,8 +194,12 @@ TLine = record

// Average angles of the selected lines to get the rotation angle of the image
SumAngles := 0;
for I := 0 to BestLinesCount - 1 do
SumAngles := SumAngles + BestLines[I].Alpha;
// We want to make sure there are enough pixels to be meaningful
if (AccumulatedCounts div AlphaSteps) >= MinTestedPixels then
begin
for I := 0 to BestLinesCount - 1 do
SumAngles := SumAngles + BestLines[I].Alpha;
end;

Result := SumAngles / BestLinesCount;

Expand Down