Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

イメージに含まれる複数のバーコードの位置推定と読み取り

この例では、Computer Vision Toolbox™ の関数readBarcodeを使用して、イメージに含まれる 1 次元および 2 次元バーコードを検出して復号化する方法を説明します。バーコードは、データを視覚的な機械可読形式に符号化するために、広く使用されています。物品の識別、倉庫在庫の追跡、コンプライアンスの追跡など、多くの用途に役立っています。1 次元バーコードの場合、関数 readBarcode はバーコードの端点の位置を返します。2 次元バーコードの場合、この関数はファインダー パターンの位置を返します。この例では、イメージに含まれる複数のバーコードの位置推定を行うために、2 つのアプローチを使用します。第 1 のアプローチはクラスタリングに基づいており、さまざまなイメージング条件に対してよりロバストです。Statistics and Machine Learning Toolbox™ が必要です。第 2 のアプローチはセグメンテーションに基づくワークフローを使用します。イメージング条件に基づいたパラメーター調整が必要になることがあります。

関数 readBarcode を使用したバーコード検出

イメージから QR コードを読み取ります。

I = imread("barcodeQR.jpg");

% Search the image for a QR Code.
[msg, ~, loc] = readBarcode(I);

% Annotate the image with the decoded message.
xyText =  loc(2,:);
Imsg = insertText(I, xyText, msg, "BoxOpacity", 1, "FontSize", 25);

% Insert filled circles at the finder pattern locations.
Imsg = insertShape(Imsg, "FilledCircle", [loc, ...
    repmat(10, length(loc), 1)], "Color", "red", "Opacity", 1);

% Display image.
imshow(Imsg)

Figure contains an axes object. The axes object contains an object of type image.

イメージから 1 次元バーコードを読み取ります。

I = imread("barcode1D.jpg");

% Read the 1-D barcode and determine the format..
[msg, format, locs] = readBarcode(I);

% Display the detected message and format.
disp("Detected format and message: " + format + ", " + msg)
Detected format and message: EAN-13, 1234567890128
% Insert a line to show the scan row of the barcode.
xyBegin = locs(1,:); imSize = size(I);
I = insertShape(I,"Line",[1 xyBegin(2) imSize(2) xyBegin(2)], ...
    "LineWidth", 7);

% Insert markers at the end locations of the barcode.
I = insertShape(I, "FilledCircle", [locs, ...
    repmat(10, length(locs), 1)], "Color", "red", "Opacity", 1);

% Display image.
imshow(I)

Figure contains an axes object. The axes object contains an object of type image.

バーコード検出の改善

正常な検出を行うためには、バーコードがはっきりと見えていなければなりません。また、バーコードはできる限り水平位置または垂直位置に合わせなければなりません。関数 readBarcode は本質的に、1 次元バーコード (リニア バーコード) よりも、2 次元コード (マトリックス コード) の回転に対してロバストです。たとえば、このイメージからはバーコードを検出できません。

I = imread("rotated1DBarcode.jpg");

% Display the image.
imshow(I)

Figure contains an axes object. The axes object contains an object of type image.

% Pass the image to the readBarcode function.
readBarcode(I)
ans = 
""

imrotate を使用してイメージを回転させ、ほぼ水平にします。回転させたイメージに対して、readBarcode を使用します。

% Rotate the image by 30 degrees clockwise.
Irot = imrotate(I, -30);

% Display the rotated image.
imshow(Irot)

Figure contains an axes object. The axes object contains an object of type image.

% Pass the rotated image to the readBarcode function.
readBarcode(Irot)
ans = 
"012345678905"

複数のバーコードの検出

関数 readBarcode は、1 つのイメージにつき、バーコードを 1 つのみ検出します。複数のバーコードを検出するためには、関心領域 (ROI) を指定しなければなりません。ROI を指定する際には、関数drawrectangleを使用することで ROI を対話的に特定できます。イメージ解析の手法を使用して、イメージに含まれる複数のバーコードの ROI を検出することもできます。

ROI の対話的な特定

I = imread("multiple1DBarcodes.jpg");

関数 drawrectangle を使用して、四角形を描画し、そのパラメーターを取得します。

roi1 = drawrectangle;

pos = roi1.Position;

% ROIs obtained using drawrectangle
roi = [180 100 330 180
    180 320 330 180
    180 550 330 180];

imSize = size(I);
for i = 1:size(roi,1)
    [msg, format, locs] = readBarcode(I, roi(i,:));
    disp("Decoded format and message: " + format + ", " + msg)
    
    % Insert a line to indicate the scan row of the barcode.
    xyBegin = locs(1,:);
    I = insertShape(I,"Line",[1 xyBegin(2) imSize(2) xyBegin(2)], ...
        "LineWidth", 5);
    
    % Annotate image with decoded message.
    I = insertText(I, xyBegin, msg, "BoxOpacity", 1, "FontSize", 20);
end
Decoded format and message: UPC-A, 012345678905
Decoded format and message: EAN-13, 4567891324562
Decoded format and message: CODE-39, ABC-123
imshow(I)

Figure contains an axes object. The axes object contains an object of type image.

イメージ解析による ROI の特定

イメージ解析の手法を使用して、複数のバーコードの検出を自動化します。そのためには、イメージに含まれる複数のバーコードの位置推定、バーコードの向きの特定、向きの補正を行う必要があります。前処理を行わないと、複数の回転したバーコードを含むイメージにおいて、バーコードを検出することはできません。

I = imread("multiple1DBarcodesRotated.jpg");
Igray = im2gray(I);

% Display the image.
imshow(I)

Figure contains an axes object. The axes object contains an object of type image.

% Pass the unprocessed image to the readBarcode function.
readBarcode(Igray, '1D')
ans = 
""

未処理のイメージ上で検出を行っても、何も検出されません。

手順 1: MSER を使用して、バーコードの候補となる領域を検出します

関数detectMSERFeaturesを使用して、イメージの関心領域を検出します。次に、縦横比などの特定の条件に基づいて、関心領域を除外できます。絞り込まれた結果のバイナリ イメージを以降の処理に使用できます。

% Detect MSER features.
[~, cc] = detectMSERFeatures(Igray);

% Compute region properties MajorAxisLength and MinorAxisLength.
regionStatistics = regionprops(cc, 'MajorAxisLength', 'MinorAxisLength');

% Filter out components that have a low aspect ratio as unsuitable
% candidates for the bars in the barcode.
minAspectRatio = 10;
candidateRegions = find(([regionStatistics.MajorAxisLength]./[regionStatistics.MinorAxisLength]) > minAspectRatio);

% Binary image to store the filtered components.
BW = false(size(Igray));

% Update the binary image.
for i = 1:length(candidateRegions)
    BW(cc.PixelIdxList{candidateRegions(i)}) = true;
end

% Display the binary image with the filtered components.
imshow(BW)
title("Candidate regions for the barcodes")

Figure contains an axes object. The axes object with title Candidate regions for the barcodes contains an object of type image.

手順 2: ハフ変換を使用して、バーコードのライン セグメントを抽出します

関数edgeを使用して、イメージ内の顕著なエッジを検出します。次に、ハフ変換を使用して、関心対象のラインを見つけます。これらのラインが、バーコードの垂直バーとして考えられる候補となります。

% Perform hough transform.
BW = edge(BW,'canny');
[H,T,R] = hough(BW);

% Display the result of the edge detection operation.
imshow(BW)

Figure contains an axes object. The axes object contains an object of type image.

% Determine the size of the suppression neighborhood.
reductionRatio = 500;
nhSize = floor(size(H)/reductionRatio);
idx = mod(nhSize,2) < 1;
nhSize(idx) = nhSize(idx) + 1;

% Identify the peaks in the Hough transform.
P  = houghpeaks(H,length(candidateRegions),'NHoodSize',nhSize);

% Detect the lines based on the detected peaks.
lines = houghlines(BW,T,R,P);

% Display the lines detected using the houghlines function.
Ihoughlines = ones(size(BW));

% Start and end points of the detected lines.
startPts = reshape([lines(:).point1], 2, length(lines))';
endPts = reshape([lines(:).point2], 2, length(lines))';

Ihoughlines = insertShape(Ihoughlines, 'Line', [startPts, endPts], ...
    'LineWidth', 2, 'Color', 'green');

% Display the original image overlayed with the detected lines.
Ibarlines = imoverlay(I, ~Ihoughlines(:,:,1));
imshow(Ibarlines)

Figure contains an axes object. The axes object contains an object of type image.

手順 3: イメージに含まれるバーコードの位置を推定します

ライン セグメントを抽出した後で、イメージに含まれる個々のバーコードの位置推定を行う手法が 2 つあります。

  • メソッド 1: クラスタリングに基づいた手法であり、Statistics and Machine Learning Toolbox™ の機能を使用して、個々のバーコードを識別します。上記のイメージ解析手法を使用して検出された外れ値に対しては、この手法のほうがよりロバストです。また、パラメーター調整をすることなく、幅広いイメージング条件に対応できます。

  • メソッド 2: セグメンテーションに基づいたワークフローで、個々のバーコードを分離します。この手法では、抽出されたバーコードの位置推定と回転補正に、別のイメージ解析手法を使用します。これはかなり有効に機能しますが、外れ値の検出を防ぐために、ある程度のパラメーター調整が必要になる場合があります。

メソッド 1: クラスタリング ベースのワークフロー

このワークフローは 2 つの手順があります。

1.バーコードのライン セグメントの二等分線を特定します

一般的な方法ではライン (ハフ変換を使用して取得したもの) を直接使用してバーコードの位置推定を行いますが、この手法ではさらに、ラインを使用して、各ラインの垂直二等分線を検出します。これらの二等分線は直交座標空間における点として表されるため、個々のバーコードの識別に適しています。二等分線を使用することで、異なるバーコードに属するよく似たラインを誤分類することが少なくなるため、個々のバーコードの識別がよりロバストになります。

2.二等分線のクラスタリングを行い、個々のバーコードを識別します

バーコードのバーはすべて互いにほぼ平行なので、それぞれのバーの二等分線は、理想的には同じラインになるはずです。そのため、それぞれに対応する点が、1 つの点の周囲に密集するはずです。実際には、これらの二等分線はセグメントごとに異なりますが、密度に基づいたクラスタリング アルゴリズムを使用できる程度には相似しています。このクラスタリング処理の実行結果として、一連のクラスターが得られます。それぞれのクラスターが個々のバーコードを指しています。この例では、関数dbscan (Statistics and Machine Learning Toolbox)を使用します。この関数では、事前にクラスター数を知っている必要がありません。この例では、それぞれのクラスター (バーコード) を視覚化します。

この例では、Statistics and Machine Learning Toolbox™ のライセンスをチェックします。ライセンスが見つかると、この例ではクラスタリングの手法を使用します。そうでない場合、この例ではセグメンテーションの手法を使用します。

useClustering = license('test','statistics_toolbox');

if useClustering
    [boundingBox, orientation, Iclusters] = clusteringLocalization(lines, size(I));
    
    % Display the detected clusters.
    imshow(Iclusters)
else
    disp("The clustering based workflow requires a license for the Statistics and Machine Learning Toolbox")
end

Figure contains an axes object. The axes object contains an object of type image.

メソッド 2: セグメンテーション ベースのワークフロー

背景のノイズとばらつきを取り除いてから、imdilate などのモルフォロジー演算を使用して、検出された垂直バーをバーコードごとにグループ化します。この例では、関数regionpropsを使用して、各バーコードの境界ボックスと向きを特定します。その結果を使用して、元のイメージから個々のバーコードを切り取り、向きをほぼ水平にします。

if ~useClustering
    [boundingBox, orientation, Idilated] = segmentationLocalization(Ihoughlines);
    
    % Display the dilated image.
    imshow(Idilated)
end

手順 4: バーコードを切り取り、回転を補正します

セグメンテーションから取得した境界ボックスを使用して、元のイメージからバーコードを切り取ります。向きの結果を使用して、バーコードをほぼ水平に揃えます。

% Localize and rotate the barcodes in the image.
correctedImages = cell(1, length(orientation));

% Store the cropped and rotation corrected images of the barcodes.
for i = 1:length(orientation)
    
    I = insertShape(I, 'Rectangle', boundingBox(i,:), 'LineWidth',3, 'Color', 'red');
    
    if orientation(i) > 0
        orientation(i) = -(90 - orientation(i));
    else
        orientation(i) = 90 + orientation(i);
    end
    
    % Crop the barcode from the original image and rotate it using the
    % detected orientation.
    correctedImages{i} = imrotate(imcrop(Igray,boundingBox(i,:)), orientation(i));
end

% Display the image with the localized barcodes.
imshow(I)

Figure contains an axes object. The axes object contains an object of type image.

手順 5: 切り取りと回転の補正を行ったイメージで、バーコードを検出します

次に、切り取りと回転の補正を行ったバーコードのイメージを関数 readBarcode で使用して、復号化します。

% Pass each of the images to the readBarcode function.
for i = 1:length(correctedImages)
    [msg, format, ~] = readBarcode(correctedImages{i}, '1D');
    disp("Decoded format and message: " + format + ", " + msg)
end
Decoded format and message: UPC-A, 012345678905
Decoded format and message: EAN-13, 4567891324562
Decoded format and message: CODE-39, ABC-123

この例では、関数 readBarcode を使用して、イメージに含まれるバーコードの検出、復号化、位置推定を行う方法を説明しました。この関数は、バーコードの配置がほぼ水平または垂直であれば有効に機能しますが、バーコードが回転して見える場合には、追加の前処理が必要です。上記の前処理手順は、イメージ内で整列していない複数のバーコードを扱う際に、良い出発点となります。

サポート関数

clusteringLocalization はクラスタリング ベースのワークフローを使用して、個々のバーコードの位置推定を行います。

function [boundingBox, orientation, Iclusters] = clusteringLocalization(lines, imSize)

%------------------------------------------------------------------------
% Determine Bisectors of Barcode Line Segments
%------------------------------------------------------------------------

% Table to store the properties of the bisectors of the detected lines.
linesBisector = array2table(zeros(length(lines), 4), 'VariableNames', {'theta', 'rho', 'x', 'y'});

% Use the orientation values of the lines to determine the orientation.
% values of the bisectors
idxNeg = find([lines.theta] < 0);
idxPos = find([lines.theta] >= 0);

negAngles = 90 + [lines(idxNeg).theta];
linesBisector.theta(idxNeg) = negAngles;

posAngles = [lines(idxPos).theta] - 90;
linesBisector.theta(idxPos) = posAngles;

% Determine the midpoints of the detected lines.
midPts = zeros(length(lines),2);

% Determine the 'rho' values of the bisectors.
for i = 1:length(lines)
    midPts(i,:) = (lines(i).point1 + lines(i).point2)/2;
    linesBisector.rho(i) = abs(midPts(i,2) - tand(lines(i).theta) * midPts(i,1))/...
        ((tand(lines(i).theta)^2 + 1) ^ 0.5);
end

% Update the [x,y] locations of the bisectors using their polar
% coordinates.
[linesBisector.x, linesBisector.y] = pol2cart(deg2rad(linesBisector.theta),linesBisector.rho,'ro');

%------------------------------------------------------------------------
% Perform Clustering on the Bisectors to Identity the Individual Barcodes
%------------------------------------------------------------------------

% Store the [x,y] data of the bisectors to be used for clustering.
X = [linesBisector.x,linesBisector.y];

% Get pairwise distance between the points
D = pdist2(X,X);

% Perform density-based spatial clustering to separate the different
% barcodes in the image.
searchRadius = max(imSize/5);
minPoints = 10;
idx = dbscan(D,searchRadius, minPoints);

% Identify the number of clusters (barcodes).
numClusters = unique(idx(idx > 0));

% Store the endpoints of the detected lines.
dataXY = cell(1, length(numClusters));

% Image to show the detected clusters (barcodes).
Iclusters = ones(imSize);

for i = 1:length(numClusters)
    classIdx = find(idx == i);
    
    rgbColor = rand(1,3);
    startPts = reshape([lines(classIdx).point1], 2, length(classIdx))';
    endPts = reshape([lines(classIdx).point2], 2, length(classIdx))';
    
    % Insert lines corresponding to the current cluster (barcode).
    Iclusters = insertShape(Iclusters, 'Line', [startPts, endPts], ...
        'LineWidth', 2, 'Color', rgbColor);
    
    % Update the endpoints of the lines in each cluster (barcode).
    dataXY{i} = [startPts; endPts];
end

%------------------------------------------------------------------------
% Localization parameters for the barcode
%------------------------------------------------------------------------

orientation = zeros(1,length(numClusters));
boundingBox = zeros(length(numClusters), 4);

% Padding the cropped images of barcodes.
padding = 40;

% Determine the ROI and orientation of the individual clusters (barcodes).
for i = 1:length(numClusters)
    
    % Bounding box coordinates with padding.
    x1 = min(dataXY{i}(:,1)) - padding;
    x2 = max(dataXY{i}(:,1)) + padding;
    y1 = min(dataXY{i}(:,2)) - padding;
    y2 = max(dataXY{i}(:,2)) + padding;
    
    boundingBox(i,:) = [x1, y1, x2-x1, y2-y1];
    
    % Orientation of the barcode.
    orientation(i) = mean(linesBisector.theta(idx == i));
    
end

end

segmentationLocalization はセグメンテーション ベースのワークフローを使用して、個々のバーコードの位置推定を行います。

function [boundingBox, orientation, Idilated] = segmentationLocalization(Ihoughlines)

%------------------------------------------------------------------------
% Use image dilation to separate the barcodes
%------------------------------------------------------------------------

% Create binary image with the detected lines.
Ibw = ~Ihoughlines(:,:,1);
Ibw(Ibw > 0) = true;

% Dilate the image using a disk structuring element.
diskRadius = 10; % Might need tuning depending on the input image.
se = strel('disk', diskRadius);
Idilated = imdilate(Ibw, se);

%------------------------------------------------------------------------
% Localization parameters for the barcode
%------------------------------------------------------------------------

% Compute region properties Orientation and BoundingBox.
regionStatistics = regionprops(Idilated, 'Orientation', 'BoundingBox');

% Padding for the cropped images of barcodes.
padding = 40;

boundingBox = zeros(length(regionStatistics), 4);

for idx = 1:length(regionStatistics)
    
    boundingBox(idx,:) = regionStatistics(idx).BoundingBox;
    
    % Bounding box coordinates with padding.
    boundingBox(idx,1) = boundingBox(idx,1) - padding;
    boundingBox(idx,2) = boundingBox(idx,2) - padding;
    boundingBox(idx,3) = boundingBox(idx,3) + 2*padding;
    boundingBox(idx,4) = boundingBox(idx,4) + 2*padding;
    
end

orientation = [regionStatistics(:).Orientation];

end

参考文献

[1] Creusot, Clement, et al. "Real-time Barcode Detection in the Wild." IEEE Winter Conference on Applications of Computer Vision, 2015.